clean up trailing whitespace, add extensive tests for tag toggle
authorHavoc Pennington <hp@redhat.com>
Wed, 4 Oct 2000 14:23:09 +0000 (14:23 +0000)
committerHavoc Pennington <hp@src.gnome.org>
Wed, 4 Oct 2000 14:23:09 +0000 (14:23 +0000)
2000-10-03  Havoc Pennington  <hp@redhat.com>

* gtk/testtextbuffer.c: clean up trailing whitespace,
add extensive tests for tag toggle iteration.

* gtk/gtktextiter.c (MAX_LINEAR_SCAN): decrease linear scan
distance

* gtk/gtktextbtree.c (gtk_text_line_next_could_contain_tag):
optimize case where the tag root is on level 1
(gtk_text_line_previous_could_contain_tag): attempt to implement
(gtk_text_line_next_could_contain_tag): Abstract out node_compare
functionality

* gtk/gtktextiter.c (gtk_text_iter_backward_to_tag_toggle):
Implement this, though not very efficiently.

* gtk/gtktextiterprivate.h: reformat

* gtk/gtktextiter.c (gtk_text_iter_get_char): return 0 on the end
iterator

* gtk/gtktextbuffer.c (gtk_text_buffer_delete_interactive): Fix
this to properly revalidate the iterators.

* gtk/gtktextview.c (gtk_text_view_delete): fix control-K to work
properly at the end of the line (and therefore on empty lines)

* gtk/gtktextbtree.c (gtk_text_btree_get_selection_bounds): Gee,
maybe we should return a value...

16 files changed:
ChangeLog
ChangeLog.pre-2-0
ChangeLog.pre-2-10
ChangeLog.pre-2-2
ChangeLog.pre-2-4
ChangeLog.pre-2-6
ChangeLog.pre-2-8
gtk/gtktextbtree.c
gtk/gtktextbuffer.c
gtk/gtktextiter.c
gtk/gtktextiterprivate.h
gtk/gtktextlayout.c
gtk/gtktextlayout.h
gtk/gtktextview.c
gtk/testtextbuffer.c
tests/testtextbuffer.c

index fd445db511271c4f1db26406f2ca3f65a99a21ad..fcec808a38366e165d0294d8521d0c46812dca4e 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,34 @@
+2000-10-03  Havoc Pennington  <hp@redhat.com>
+
+       * gtk/testtextbuffer.c: clean up trailing whitespace,
+       add extensive tests for tag toggle iteration.
+       
+       * gtk/gtktextiter.c (MAX_LINEAR_SCAN): decrease linear scan 
+       distance
+
+       * gtk/gtktextbtree.c (gtk_text_line_next_could_contain_tag):
+       optimize case where the tag root is on level 1
+       (gtk_text_line_previous_could_contain_tag): attempt to implement
+       (gtk_text_line_next_could_contain_tag): Abstract out node_compare
+       functionality
+
+       * gtk/gtktextiter.c (gtk_text_iter_backward_to_tag_toggle):
+       Implement this, though not very efficiently.
+
+       * gtk/gtktextiterprivate.h: reformat
+
+       * gtk/gtktextiter.c (gtk_text_iter_get_char): return 0 on the end
+       iterator
+
+       * gtk/gtktextbuffer.c (gtk_text_buffer_delete_interactive): Fix
+       this to properly revalidate the iterators.
+
+       * gtk/gtktextview.c (gtk_text_view_delete): fix control-K to work
+       properly at the end of the line (and therefore on empty lines)
+
+       * gtk/gtktextbtree.c (gtk_text_btree_get_selection_bounds): Gee,
+       maybe we should return a value...
+
 2000-10-03  Tor Lillqvist  <tml@iki.fi>
 
        * config.h.win32: Guard definition of alloca(). glib.h now handles
index fd445db511271c4f1db26406f2ca3f65a99a21ad..fcec808a38366e165d0294d8521d0c46812dca4e 100644 (file)
@@ -1,3 +1,34 @@
+2000-10-03  Havoc Pennington  <hp@redhat.com>
+
+       * gtk/testtextbuffer.c: clean up trailing whitespace,
+       add extensive tests for tag toggle iteration.
+       
+       * gtk/gtktextiter.c (MAX_LINEAR_SCAN): decrease linear scan 
+       distance
+
+       * gtk/gtktextbtree.c (gtk_text_line_next_could_contain_tag):
+       optimize case where the tag root is on level 1
+       (gtk_text_line_previous_could_contain_tag): attempt to implement
+       (gtk_text_line_next_could_contain_tag): Abstract out node_compare
+       functionality
+
+       * gtk/gtktextiter.c (gtk_text_iter_backward_to_tag_toggle):
+       Implement this, though not very efficiently.
+
+       * gtk/gtktextiterprivate.h: reformat
+
+       * gtk/gtktextiter.c (gtk_text_iter_get_char): return 0 on the end
+       iterator
+
+       * gtk/gtktextbuffer.c (gtk_text_buffer_delete_interactive): Fix
+       this to properly revalidate the iterators.
+
+       * gtk/gtktextview.c (gtk_text_view_delete): fix control-K to work
+       properly at the end of the line (and therefore on empty lines)
+
+       * gtk/gtktextbtree.c (gtk_text_btree_get_selection_bounds): Gee,
+       maybe we should return a value...
+
 2000-10-03  Tor Lillqvist  <tml@iki.fi>
 
        * config.h.win32: Guard definition of alloca(). glib.h now handles
index fd445db511271c4f1db26406f2ca3f65a99a21ad..fcec808a38366e165d0294d8521d0c46812dca4e 100644 (file)
@@ -1,3 +1,34 @@
+2000-10-03  Havoc Pennington  <hp@redhat.com>
+
+       * gtk/testtextbuffer.c: clean up trailing whitespace,
+       add extensive tests for tag toggle iteration.
+       
+       * gtk/gtktextiter.c (MAX_LINEAR_SCAN): decrease linear scan 
+       distance
+
+       * gtk/gtktextbtree.c (gtk_text_line_next_could_contain_tag):
+       optimize case where the tag root is on level 1
+       (gtk_text_line_previous_could_contain_tag): attempt to implement
+       (gtk_text_line_next_could_contain_tag): Abstract out node_compare
+       functionality
+
+       * gtk/gtktextiter.c (gtk_text_iter_backward_to_tag_toggle):
+       Implement this, though not very efficiently.
+
+       * gtk/gtktextiterprivate.h: reformat
+
+       * gtk/gtktextiter.c (gtk_text_iter_get_char): return 0 on the end
+       iterator
+
+       * gtk/gtktextbuffer.c (gtk_text_buffer_delete_interactive): Fix
+       this to properly revalidate the iterators.
+
+       * gtk/gtktextview.c (gtk_text_view_delete): fix control-K to work
+       properly at the end of the line (and therefore on empty lines)
+
+       * gtk/gtktextbtree.c (gtk_text_btree_get_selection_bounds): Gee,
+       maybe we should return a value...
+
 2000-10-03  Tor Lillqvist  <tml@iki.fi>
 
        * config.h.win32: Guard definition of alloca(). glib.h now handles
index fd445db511271c4f1db26406f2ca3f65a99a21ad..fcec808a38366e165d0294d8521d0c46812dca4e 100644 (file)
@@ -1,3 +1,34 @@
+2000-10-03  Havoc Pennington  <hp@redhat.com>
+
+       * gtk/testtextbuffer.c: clean up trailing whitespace,
+       add extensive tests for tag toggle iteration.
+       
+       * gtk/gtktextiter.c (MAX_LINEAR_SCAN): decrease linear scan 
+       distance
+
+       * gtk/gtktextbtree.c (gtk_text_line_next_could_contain_tag):
+       optimize case where the tag root is on level 1
+       (gtk_text_line_previous_could_contain_tag): attempt to implement
+       (gtk_text_line_next_could_contain_tag): Abstract out node_compare
+       functionality
+
+       * gtk/gtktextiter.c (gtk_text_iter_backward_to_tag_toggle):
+       Implement this, though not very efficiently.
+
+       * gtk/gtktextiterprivate.h: reformat
+
+       * gtk/gtktextiter.c (gtk_text_iter_get_char): return 0 on the end
+       iterator
+
+       * gtk/gtktextbuffer.c (gtk_text_buffer_delete_interactive): Fix
+       this to properly revalidate the iterators.
+
+       * gtk/gtktextview.c (gtk_text_view_delete): fix control-K to work
+       properly at the end of the line (and therefore on empty lines)
+
+       * gtk/gtktextbtree.c (gtk_text_btree_get_selection_bounds): Gee,
+       maybe we should return a value...
+
 2000-10-03  Tor Lillqvist  <tml@iki.fi>
 
        * config.h.win32: Guard definition of alloca(). glib.h now handles
index fd445db511271c4f1db26406f2ca3f65a99a21ad..fcec808a38366e165d0294d8521d0c46812dca4e 100644 (file)
@@ -1,3 +1,34 @@
+2000-10-03  Havoc Pennington  <hp@redhat.com>
+
+       * gtk/testtextbuffer.c: clean up trailing whitespace,
+       add extensive tests for tag toggle iteration.
+       
+       * gtk/gtktextiter.c (MAX_LINEAR_SCAN): decrease linear scan 
+       distance
+
+       * gtk/gtktextbtree.c (gtk_text_line_next_could_contain_tag):
+       optimize case where the tag root is on level 1
+       (gtk_text_line_previous_could_contain_tag): attempt to implement
+       (gtk_text_line_next_could_contain_tag): Abstract out node_compare
+       functionality
+
+       * gtk/gtktextiter.c (gtk_text_iter_backward_to_tag_toggle):
+       Implement this, though not very efficiently.
+
+       * gtk/gtktextiterprivate.h: reformat
+
+       * gtk/gtktextiter.c (gtk_text_iter_get_char): return 0 on the end
+       iterator
+
+       * gtk/gtktextbuffer.c (gtk_text_buffer_delete_interactive): Fix
+       this to properly revalidate the iterators.
+
+       * gtk/gtktextview.c (gtk_text_view_delete): fix control-K to work
+       properly at the end of the line (and therefore on empty lines)
+
+       * gtk/gtktextbtree.c (gtk_text_btree_get_selection_bounds): Gee,
+       maybe we should return a value...
+
 2000-10-03  Tor Lillqvist  <tml@iki.fi>
 
        * config.h.win32: Guard definition of alloca(). glib.h now handles
index fd445db511271c4f1db26406f2ca3f65a99a21ad..fcec808a38366e165d0294d8521d0c46812dca4e 100644 (file)
@@ -1,3 +1,34 @@
+2000-10-03  Havoc Pennington  <hp@redhat.com>
+
+       * gtk/testtextbuffer.c: clean up trailing whitespace,
+       add extensive tests for tag toggle iteration.
+       
+       * gtk/gtktextiter.c (MAX_LINEAR_SCAN): decrease linear scan 
+       distance
+
+       * gtk/gtktextbtree.c (gtk_text_line_next_could_contain_tag):
+       optimize case where the tag root is on level 1
+       (gtk_text_line_previous_could_contain_tag): attempt to implement
+       (gtk_text_line_next_could_contain_tag): Abstract out node_compare
+       functionality
+
+       * gtk/gtktextiter.c (gtk_text_iter_backward_to_tag_toggle):
+       Implement this, though not very efficiently.
+
+       * gtk/gtktextiterprivate.h: reformat
+
+       * gtk/gtktextiter.c (gtk_text_iter_get_char): return 0 on the end
+       iterator
+
+       * gtk/gtktextbuffer.c (gtk_text_buffer_delete_interactive): Fix
+       this to properly revalidate the iterators.
+
+       * gtk/gtktextview.c (gtk_text_view_delete): fix control-K to work
+       properly at the end of the line (and therefore on empty lines)
+
+       * gtk/gtktextbtree.c (gtk_text_btree_get_selection_bounds): Gee,
+       maybe we should return a value...
+
 2000-10-03  Tor Lillqvist  <tml@iki.fi>
 
        * config.h.win32: Guard definition of alloca(). glib.h now handles
index fd445db511271c4f1db26406f2ca3f65a99a21ad..fcec808a38366e165d0294d8521d0c46812dca4e 100644 (file)
@@ -1,3 +1,34 @@
+2000-10-03  Havoc Pennington  <hp@redhat.com>
+
+       * gtk/testtextbuffer.c: clean up trailing whitespace,
+       add extensive tests for tag toggle iteration.
+       
+       * gtk/gtktextiter.c (MAX_LINEAR_SCAN): decrease linear scan 
+       distance
+
+       * gtk/gtktextbtree.c (gtk_text_line_next_could_contain_tag):
+       optimize case where the tag root is on level 1
+       (gtk_text_line_previous_could_contain_tag): attempt to implement
+       (gtk_text_line_next_could_contain_tag): Abstract out node_compare
+       functionality
+
+       * gtk/gtktextiter.c (gtk_text_iter_backward_to_tag_toggle):
+       Implement this, though not very efficiently.
+
+       * gtk/gtktextiterprivate.h: reformat
+
+       * gtk/gtktextiter.c (gtk_text_iter_get_char): return 0 on the end
+       iterator
+
+       * gtk/gtktextbuffer.c (gtk_text_buffer_delete_interactive): Fix
+       this to properly revalidate the iterators.
+
+       * gtk/gtktextview.c (gtk_text_view_delete): fix control-K to work
+       properly at the end of the line (and therefore on empty lines)
+
+       * gtk/gtktextbtree.c (gtk_text_btree_get_selection_bounds): Gee,
+       maybe we should return a value...
+
 2000-10-03  Tor Lillqvist  <tml@iki.fi>
 
        * config.h.win32: Guard definition of alloca(). glib.h now handles
index a82f0d1caa7934bf05c1af01061532e754209096..4fbf2ddd717c8ad517c0be19ac0fdfae7d1ffab4 100644 (file)
@@ -414,9 +414,9 @@ gtk_text_btree_new (GtkTextTagTable *table,
   tree->mark_table = g_hash_table_new(g_str_hash, g_str_equal);
 
   /* We don't ref the buffer, since the buffer owns us;
-     we'd have some circularity issues. The buffer always
-     lasts longer than the BTree
-  */
+   * we'd have some circularity issues. The buffer always
+   * lasts longer than the BTree
+   */
   tree->buffer = buffer;
   
   {
@@ -434,7 +434,6 @@ gtk_text_btree_new (GtkTextTagTable *table,
                                                     FALSE);
     
     tree->insert_mark->body.mark.not_deleteable = TRUE;
-    
     tree->insert_mark->body.mark.visible = TRUE;
     
     tree->selection_bound_mark =
@@ -2486,6 +2485,8 @@ gtk_text_btree_get_selection_bounds (GtkTextBTree *tree,
 
       if (end)
         *end = tmp_end;
+
+      return FALSE;
     }
   else
     {
@@ -2502,7 +2503,7 @@ gtk_text_btree_get_selection_bounds (GtkTextBTree *tree,
 }
 
 void
-gtk_text_btree_place_cursor(GtkTextBTree *tree,
+gtk_text_btree_place_cursor(GtkTextBTree      *tree,
                             const GtkTextIter *iter)
 {
   GtkTextIter start, end;
@@ -2585,7 +2586,7 @@ gtk_text_btree_get_mark_by_name (GtkTextBTree *tree,
 
 void
 gtk_text_mark_set_visible (GtkTextMark       *mark,
-                           gboolean setting)
+                           gboolean           setting)
 {
   GtkTextLineSegment *seg;
   
@@ -2599,7 +2600,7 @@ gtk_text_mark_set_visible (GtkTextMark       *mark,
     {
       seg->body.mark.visible = setting;
 
-      redisplay_mark(seg);
+      redisplay_mark (seg);
     }
 }
 
@@ -3052,12 +3053,12 @@ gtk_text_line_previous (GtkTextLine *line)
         return prev;
     }
 
-  g_assert_not_reached();
+  g_assert_not_reached ();
   return NULL;
 }
 
 void
-gtk_text_line_add_data (GtkTextLine *line,
+gtk_text_line_add_data (GtkTextLine     *line,
                         GtkTextLineData *data)
 {
   g_return_if_fail(line != NULL);
@@ -3418,7 +3419,6 @@ gtk_text_line_char_to_byte (GtkTextLine *line,
   g_warning("FIXME not implemented");
 }
 
-
 /* FIXME sync with char_locate (or figure out a clean
    way to merge the two functions) */
 void
@@ -3692,6 +3692,101 @@ gtk_text_line_char_to_byte_offsets(GtkTextLine *line,
     }
 }
 
+static gint
+node_compare (GtkTextBTreeNode *lhs,
+              GtkTextBTreeNode *rhs)
+{
+  GtkTextBTreeNode *iter;
+  GtkTextBTreeNode *node;
+  GtkTextBTreeNode *common_parent;
+  GtkTextBTreeNode *parent_of_lower;
+  GtkTextBTreeNode *parent_of_higher;
+  gboolean lhs_is_lower;
+  GtkTextBTreeNode *lower;
+  GtkTextBTreeNode *higher;
+
+  /* This function assumes that lhs and rhs are not underneath each
+   * other.
+   */
+  
+  if (lhs == rhs)
+    return 0;
+  
+  if (lhs->level < rhs->level)
+    {
+      lhs_is_lower = TRUE;
+      lower = lhs;
+      higher = rhs;
+    }
+  else
+    {
+      lhs_is_lower = FALSE;
+      lower = rhs;
+      higher = lhs;
+    }
+  
+  /* Algorithm: find common parent of lhs/rhs. Save the child nodes
+   * of the common parent we used to reach the common parent; the
+   * ordering of these child nodes in the child list is the ordering
+   * of lhs and rhs.
+   */
+
+  /* Get on the same level (may be on same level already) */
+  node = lower;
+  while (node->level < higher->level)
+    node = node->parent;
+
+  g_assert (node->level == higher->level);
+  
+  g_assert (node != higher); /* Happens if lower is underneath higher */
+  
+  /* Go up until we have two children with a common parent.
+   */
+  parent_of_lower = node;
+  parent_of_higher = higher;
+
+  while (parent_of_lower->parent != parent_of_higher->parent)
+    {
+      parent_of_lower = parent_of_lower->parent;
+      parent_of_higher = parent_of_higher->parent;
+    }
+
+  g_assert (parent_of_lower->parent == parent_of_higher->parent);
+
+  common_parent = parent_of_lower->parent;
+
+  g_assert (common_parent != NULL);
+  
+  /* See which is first in the list of common_parent's children */
+  iter = common_parent->children.node;
+  while (iter != NULL)
+    {
+      if (iter == parent_of_higher)
+        {
+          /* higher is less than lower */
+          
+          if (lhs_is_lower)
+            return 1; /* lhs > rhs */
+          else
+            return -1;
+        }
+      else if (iter == parent_of_lower)
+        {
+          /* lower is less than higher */
+          
+          if (lhs_is_lower)
+            return -1; /* lhs < rhs */
+          else
+            return 1;
+        }
+      
+      iter = iter->next;
+    }
+
+  g_assert_not_reached ();
+  return 0;
+}
+
 /* remember that tag == NULL means "any tag" */
 GtkTextLine*
 gtk_text_line_next_could_contain_tag(GtkTextLine *line,
@@ -3710,7 +3805,8 @@ gtk_text_line_next_could_contain_tag(GtkTextLine *line,
   if (tag == NULL)
     {
       /* Right now we can only offer linear-search if the user wants
-         to know about any tag toggle at all. */
+       * to know about any tag toggle at all.
+       */
       return gtk_text_line_next (line);
     }
   
@@ -3733,6 +3829,9 @@ gtk_text_line_next_could_contain_tag(GtkTextLine *line,
 
   if (info->tag_root == NULL)
     return NULL;
+
+  if (info->tag_root == line->parent)
+    return NULL; /* we were at the last line under the tag root */
   
   /* We need to go up out of this node, and on to the next one with
      toggles for the target tag. If we're below the tag root, we need to
@@ -3773,57 +3872,25 @@ gtk_text_line_next_could_contain_tag(GtkTextLine *line,
     }
   else
     {
-      GtkTextBTreeNode * iter;
-      GtkTextBTreeNode * common_parent;
-      GtkTextBTreeNode * parent_of_tag_root;
-      GtkTextBTreeNode * parent_of_node;
-
-      /* Find common parent between our current line, and the tag
-         root. Save the child nodes of the common parent we used to get
-         to the common parent; we then use these two child nodes to
-         determine whether the ordering of the tag root and the current
-         line in the tree. (Nice code cleanup: write
-         gtk_btree_node_compare() to compute node ordering.)
-      */
-
-      /* Get on the same level */
-      node = line->parent;
-      while (node->level < info->tag_root->level)
-        node = node->parent;
-
-      common_parent = info->tag_root->parent;
+      gint ordering;
 
-      /* Find common parent, and children of that parent above
-         tag root and our current node */
-      parent_of_node = node;
-      parent_of_tag_root = info->tag_root;
+      ordering = node_compare (line->parent, info->tag_root);
 
-      while (node->parent != common_parent)
+      if (ordering < 0)
         {
-          parent_of_node = node;
-          parent_of_tag_root = common_parent;
-          node = node->parent;
-          common_parent = common_parent->parent;
+          /* Tag root is ahead of us, so search there. */
+          node = info->tag_root;
+          goto found;
         }
-
-      /* See which is first in the list of common_parent's children */
-      iter = common_parent->children.node;
-      while (iter != NULL)
+      else
         {
-          if (iter == parent_of_tag_root)
-            return NULL; /* Tag root was before us in the tree */
-          else if (iter == parent_of_node)
-            {
-              /* We want the first inside-tag-root node,
-                 since we're before the tag root */
-              node = info->tag_root;
-              goto found;
-            }
-
-          iter = iter->next;
+          /* Tag root is after us, so no more lines that
+           * could contain the tag.
+           */
+          return NULL;
         }
-      
-      return NULL;
+
+      g_assert_not_reached ();
     }
 
  found:
@@ -3831,42 +3898,261 @@ gtk_text_line_next_could_contain_tag(GtkTextLine *line,
   g_assert(node != NULL);
   
   /* We have to find the first sub-node of this node that contains
-     the target tag. */
+   * the target tag.
+   */
 
- continue_outer_loop:
   while (node->level > 0)
     {
-      g_assert(node != NULL); /* If this fails, it likely means an
-                                 incorrect tag summary led us on a
-                                 wild goose chase down this branch of
-                                 the tree. */
+      g_assert (node != NULL); /* If this fails, it likely means an
+                                  incorrect tag summary led us on a
+                                  wild goose chase down this branch of
+                                  the tree. */
       node = node->children.node;
       while (node != NULL)
         {
-          if (gtk_text_btree_node_has_tag(node, tag))
-            goto continue_outer_loop;
+          if (gtk_text_btree_node_has_tag (node, tag))
+            break;
           node = node->next;
         }
-      g_assert(node != NULL);
     }
 
-  g_assert(node != NULL);
-  g_assert(node->level == 0);
+  g_assert (node != NULL);
+  g_assert (node->level == 0);
   
   return node->children.line;
 }
-      
+
+static GtkTextLine*
+prev_line_under_node (GtkTextBTreeNode *node,
+                      GtkTextLine      *line)
+{
+  GtkTextLine *prev;
+
+  prev = node->children.line;
+
+  g_assert (prev);
+
+  if (prev != line)
+    {
+      while (prev->next != line)
+        prev = prev->next;
+
+      return prev;
+    }
+
+  return NULL;
+}
+
 GtkTextLine*
-gtk_text_line_previous_could_contain_tag(GtkTextLine *line,
-                                         GtkTextBTree *tree,
-                                         GtkTextTag  *tag)
+gtk_text_line_previous_could_contain_tag (GtkTextLine  *line,
+                                          GtkTextBTree *tree,
+                                          GtkTextTag   *tag)
 {
-  g_warning("FIXME");
+  GtkTextBTreeNode *node;
+  GtkTextBTreeNode *found_node = NULL;
+  GtkTextTagInfo *info;
+  gboolean below_tag_root;
+  GtkTextLine *prev;
+  GtkTextBTreeNode *line_ancestor;
+  GtkTextBTreeNode *line_ancestor_parent;
+  
+  /* See next_could_contain_tag() for more extensive comments
+   * on what's going on here.
+   */
+  
+  g_return_val_if_fail(line != NULL, NULL);
+  
+  if (gtk_debug_flags & GTK_DEBUG_TEXT)
+    gtk_text_btree_check (tree);
+  
+  if (tag == NULL)
+    {
+      /* Right now we can only offer linear-search if the user wants
+       * to know about any tag toggle at all.
+       */
+      return gtk_text_line_previous (line);
+    }
+
+  /* Return same-node line, if any. */
+  prev = prev_line_under_node (line->parent, line);
+  if (prev)
+    return prev;
+  
+  info = gtk_text_btree_get_existing_tag_info (tree, tag);
+  if (info == NULL)
+    return NULL;
+
+  if (info->tag_root == NULL)
+    return NULL;
+
+  if (info->tag_root == line->parent)
+    return NULL; /* we were at the first line under the tag root */
+  
+  /* Are we below the tag root */
+  node = line->parent;
+  below_tag_root = FALSE;
+  while (node != NULL)
+    {
+      if (node == info->tag_root)
+        {
+          below_tag_root = TRUE;
+          break;
+        }
+
+      node = node->parent;
+    }
+
+  if (below_tag_root)
+    {
+      /* Look for a previous node under this tag root that has our
+       * tag.
+       */
+
+      /* this assertion holds because line->parent is not the
+       * tag root, we are below the tag root, and the tag
+       * root exists.
+       */
+      g_assert (line->parent->parent != NULL);
+
+      line_ancestor = line->parent;
+      line_ancestor_parent = line->parent->parent;
+
+      node = line_ancestor_parent->children.node;
+      while (node != line_ancestor &&
+             line_ancestor != info->tag_root)
+        {
+          GSList *child_nodes = NULL;
+          GSList *tmp;
+          
+          /* Create reverse-order list of nodes before
+           * line_ancestor
+           */
+          while (node != line_ancestor
+                 && node != NULL)
+            {
+              child_nodes = g_slist_prepend (child_nodes, node);
+
+              node = node->next;
+            }
+          
+          /* Try to find a node with our tag on it in the list */
+          tmp = child_nodes;
+          while (tmp != NULL)
+            {
+              GtkTextBTreeNode *this_node = tmp->data;
+
+              g_assert (this_node != line_ancestor);
+              
+              if (gtk_text_btree_node_has_tag (this_node, tag))
+                {
+                  found_node = this_node;
+                  g_slist_free (child_nodes);
+                  goto found;
+                }
+              
+              tmp = g_slist_next (tmp);
+            }
+
+          g_slist_free (child_nodes);
+
+          /* Didn't find anything on this level; go up one level. */
+          line_ancestor = line_ancestor_parent;
+          line_ancestor_parent = line_ancestor->parent;
+
+          node = line_ancestor_parent->children.node;
+        }
 
+      /* No dice. */
+      return NULL;
+    }
+  else
+    {
+      gint ordering;
+
+      ordering = node_compare (line->parent, info->tag_root);
+
+      if (ordering < 0)
+        {
+          /* Tag root is ahead of us, so no more lines
+           * with this tag.
+           */
+          return NULL;
+        }
+      else
+        {
+          /* Tag root is after us, so grab last tagged
+           * line underneath the tag root.
+           */
+          found_node = info->tag_root;
+          goto found;
+        }
+
+      g_assert_not_reached ();
+    }
+
+ found:
+  
+  g_assert (found_node != NULL);
+  
+  /* We have to find the last sub-node of this node that contains
+   * the target tag.
+   */
+  node = found_node;
+  
+  while (node->level > 0)
+    {
+      GSList *child_nodes = NULL;
+      GSList *iter;
+      g_assert (node != NULL); /* If this fails, it likely means an
+                                  incorrect tag summary led us on a
+                                  wild goose chase down this branch of
+                                  the tree. */
+
+      node = node->children.node;
+      while (node != NULL)
+        {
+          child_nodes = g_slist_prepend (child_nodes, node);
+          node = node->next;
+        }
+
+      node = NULL; /* detect failure to find a child node. */
+      
+      iter = child_nodes;
+      while (iter != NULL)
+        {
+          if (gtk_text_btree_node_has_tag (iter->data, tag))
+            {
+              /* recurse into this node. */
+              node = iter->data;
+              break;
+            }
+
+          iter = g_slist_next (iter);
+        }
+
+      g_slist_free (child_nodes);
+
+      g_assert (node != NULL);
+    }
+
+  g_assert (node != NULL);
+  g_assert (node->level == 0);
+
+  /* this assertion is correct, but slow. */
+  /* g_assert (node_compare (node, line->parent) < 0); */
+  
+  /* Return last line in this node. */
+
+  prev = node->children.line;
+  while (prev->next)
+    prev = prev->next;
+
+  return prev;
 }
 
 /*
- * Non-public function implementations */
+ * Non-public function implementations
+ */
 
 static void
 summary_list_destroy(Summary *summary)
@@ -6260,6 +6546,11 @@ gtk_text_btree_spew (GtkTextBTree *tree)
 
         list = g_slist_next (list);
       }
+    
+    if (tree->tag_infos == NULL)
+      {
+        printf ("  (no tags in the tree)\n");
+      }
   }
 
   printf("=================== Tree nodes\n");
@@ -6301,11 +6592,17 @@ gtk_text_btree_spew_line_short (GtkTextLine *line, int indent)
         }
       else if (seg->type == &gtk_text_right_mark_type)
         {
-          printf("%s right mark `%s'\n", spaces, seg->body.mark.name);
+          printf("%s right mark `%s' visible: %d\n",
+                 spaces,
+                 seg->body.mark.name,
+                 seg->body.mark.visible);
         }
       else if (seg->type == &gtk_text_left_mark_type)
         {
-          printf("%s left mark `%s'\n", spaces, seg->body.mark.name);
+          printf("%s left mark `%s' visible: %d\n",
+                 spaces,
+                 seg->body.mark.name,
+                 seg->body.mark.visible);
         }
       else if (seg->type == &gtk_text_toggle_on_type ||
                seg->type == &gtk_text_toggle_off_type)
@@ -6397,11 +6694,17 @@ gtk_text_btree_spew_segment(GtkTextBTree* tree, GtkTextLineSegment * seg)
     }
   else if (seg->type == &gtk_text_right_mark_type)
     {
-      printf("       right mark `%s'\n", seg->body.mark.name);
+      printf("       right mark `%s' visible: %d not_deleteable: %d\n",
+             seg->body.mark.name,
+             seg->body.mark.visible,
+             seg->body.mark.not_deleteable);
     }
   else if (seg->type == &gtk_text_left_mark_type)
     {
-      printf("       left mark `%s'\n", seg->body.mark.name);
+      printf("       left mark `%s' visible: %d not_deleteable: %d\n",
+             seg->body.mark.name,
+             seg->body.mark.visible,
+             seg->body.mark.not_deleteable);
     }
   else if (seg->type == &gtk_text_toggle_on_type ||
            seg->type == &gtk_text_toggle_off_type)
index 9a70c78a1098d693ebb99a06e9c4de593526d23e..627341f431ff01d852c67f2b64c83558cc04a5f4 100644 (file)
@@ -821,8 +821,8 @@ gtk_text_buffer_emit_delete (GtkTextBuffer *buffer,
  **/
 void
 gtk_text_buffer_delete (GtkTextBuffer *buffer,
-                        GtkTextIter *start,
-                        GtkTextIter *end)
+                        GtkTextIter   *start,
+                        GtkTextIter   *end)
 {
   g_return_if_fail(GTK_IS_TEXT_BUFFER(buffer));
   g_return_if_fail(start != NULL);
@@ -840,7 +840,9 @@ gtk_text_buffer_delete (GtkTextBuffer *buffer,
  * 
  * Deletes all <emphasis>editable</emphasis> text in the given range.
  * Calls gtk_text_buffer_delete() for each editable sub-range of
- * [@start,@end).
+ * [@start,@end). @start and @end are revalidated to point to
+ * the location of the last deleted range, or left untouched if
+ * no text was deleted.
  * 
  * Return value: whether some text was actually deleted
  **/
@@ -904,6 +906,10 @@ gtk_text_buffer_delete_interactive (GtkTextBuffer *buffer,
                   gtk_text_buffer_emit_delete (buffer, &start, &iter, TRUE);
                   
                   deleted_stuff = TRUE;
+
+                  /* revalidate user's iterators. */
+                  *start_iter = start;
+                  *end_iter = iter;
                 }
 
               break;
@@ -923,6 +929,10 @@ gtk_text_buffer_delete_interactive (GtkTextBuffer *buffer,
 
           current_state = FALSE;
           deleted_stuff = TRUE;
+
+          /* revalidate user's iterators. */
+          *start_iter = start;
+          *end_iter = iter;
         }
       else
         {
@@ -1478,7 +1488,7 @@ gtk_text_buffer_real_apply_tag (GtkTextBuffer *buffer,
                                 const GtkTextIter *start,
                                 const GtkTextIter *end)
 {
-  gtk_text_btree_tag(start, end, tag, TRUE);
+  gtk_text_btree_tag (start, end, tag, TRUE);
 }
 
 static void
@@ -1487,7 +1497,7 @@ gtk_text_buffer_real_remove_tag (GtkTextBuffer *buffer,
                                  const GtkTextIter *start,
                                  const GtkTextIter *end)
 {
-  gtk_text_btree_tag(start, end, tag, FALSE);
+  gtk_text_btree_tag (start, end, tag, FALSE);
 }
 
 
@@ -1500,12 +1510,14 @@ gtk_text_buffer_emit_tag(GtkTextBuffer *buffer,
 {
   g_return_if_fail(tag != NULL);
 
+  gtk_text_iter_reorder (start, end);
+  
   if (apply)
-    gtk_signal_emit(GTK_OBJECT(buffer), signals[APPLY_TAG],
-                    tag, start, end);
+    gtk_signal_emit (GTK_OBJECT(buffer), signals[APPLY_TAG],
+                     tag, start, end);
   else
-    gtk_signal_emit(GTK_OBJECT(buffer), signals[REMOVE_TAG],
-                    tag, start, end);
+    gtk_signal_emit (GTK_OBJECT(buffer), signals[REMOVE_TAG],
+                     tag, start, end);
 }
 
 
index b2ec510fe8e760a4ce67a50b0bbfb8c335fe1a30..e061e1978e62c7c57dccfcdf6608ae166b0e17e0 100644 (file)
@@ -33,7 +33,8 @@
 
 typedef struct _GtkTextRealIter GtkTextRealIter;
 
-struct _GtkTextRealIter {
+struct _GtkTextRealIter
+{
   /* Always-valid information */
   GtkTextBTree *tree;
   GtkTextLine *line;
@@ -53,7 +54,7 @@ struct _GtkTextRealIter {
   /* Valid if the segments_changed_stamp is up-to-date */
   GtkTextLineSegment *segment;     /* indexable segment we index */
   GtkTextLineSegment *any_segment; /* first segment in our location,
-                                   maybe same as "segment" */
+                                      maybe same as "segment" */
   /* One of these will always be valid if segments_changed_stamp is
      up-to-date. If invalid, they are -1.
 
@@ -94,7 +95,7 @@ iter_set_from_byte_offset(GtkTextRealIter *iter,
 {
   iter_set_common(iter, line);
 
-  gtk_text_line_byte_locate(iter->line,
+  gtk_text_line_byte_locate (iter->line,
                              byte_offset,
                              &iter->segment,
                              &iter->any_segment,
@@ -110,7 +111,7 @@ iter_set_from_char_offset(GtkTextRealIter *iter,
 {
   iter_set_common(iter, line);
 
-  gtk_text_line_char_locate(iter->line,
+  gtk_text_line_char_locate (iter->line,
                              char_offset,
                              &iter->segment,
                              &iter->any_segment,
@@ -362,6 +363,12 @@ ensure_byte_offsets(GtkTextRealIter *iter)
     }
 }
 
+static inline gboolean
+is_segment_start (GtkTextRealIter *real)
+{
+  return real->segment_byte_offset == 0 || real->segment_char_offset == 0;
+}
+
 #if 1
 static void
 check_invariants(const GtkTextIter *iter)
@@ -689,12 +696,15 @@ gtk_text_iter_get_line_index(const GtkTextIter *iter)
  * Returns the Unicode character at this iterator.  (Equivalent to
  * operator* on a C++ iterator.)  If the iterator points at a
  * non-character element, such as an image embedded in the buffer, the
- * Unicode "unknown" character 0xFFFD is returned.
+ * Unicode "unknown" character 0xFFFD is returned. If invoked on
+ * the end iterator, zero is returned; zero is not a valid Unicode character.
+ * So you can write a loop which ends when gtk_text_iter_get_char()
+ * returns 0.
  * 
- * Return value: a Unicode character
+ * Return value: a Unicode character, or 0 if @iter is not dereferenceable
  **/
 gunichar
-gtk_text_iter_get_char(const GtkTextIter *iter)
+gtk_text_iter_get_char (const GtkTextIter *iter)
 {
   GtkTextRealIter *real;
 
@@ -705,12 +715,11 @@ gtk_text_iter_get_char(const GtkTextIter *iter)
   if (real == NULL)
     return 0;
 
-  check_invariants(iter);
-  
-  /* FIXME probably want to special-case the end iterator
-     and either have an error or return 0 */
-  
-  if (real->segment->type == &gtk_text_char_type)
+  check_invariants(iter);  
+
+  if (gtk_text_iter_is_last (iter))
+    return 0;
+  else if (real->segment->type == &gtk_text_char_type)
     {
       ensure_byte_offsets(real);
       
@@ -1392,13 +1401,13 @@ gtk_text_iter_get_attributes (const GtkTextIter  *iter,
  * (MOVEMENT OCCURRED && NEW ITER IS DEREFERENCEABLE)
  */
 static gboolean
-forward_line_leaving_caches_unmodified(GtkTextRealIter *real)
+forward_line_leaving_caches_unmodified (GtkTextRealIter *real)
 {
   GtkTextLine *new_line;
   
-  new_line = gtk_text_line_next(real->line);
+  new_line = gtk_text_line_next (real->line);
 
-  g_assert(new_line != real->line);
+  g_assert (new_line != real->line);
   
   if (new_line != NULL)
     {      
@@ -1442,11 +1451,56 @@ forward_line_leaving_caches_unmodified(GtkTextRealIter *real)
     }
 }
 
+
+/* The return value of this indicates WHETHER WE MOVED.
+ * The return value of public functions indicates
+ * (MOVEMENT OCCURRED && NEW ITER IS DEREFERENCEABLE)
+ */
+static gboolean
+backward_line_leaving_caches_unmodified (GtkTextRealIter *real)
+{
+  GtkTextLine *new_line;
+  
+  new_line = gtk_text_line_previous (real->line);
+
+  g_assert (new_line != real->line);
+  
+  if (new_line != NULL)
+    {      
+      real->line = new_line;
+
+      real->line_byte_offset = 0;
+      real->line_char_offset = 0;
+      
+      real->segment_byte_offset = 0;
+      real->segment_char_offset = 0;
+      
+      /* Find first segments in new line */
+      real->any_segment = real->line->segments;
+      real->segment = real->any_segment;
+      while (real->segment->char_count == 0)
+        real->segment = real->segment->next;
+
+      return TRUE;
+    }
+  else
+    {
+      /* There is no way to move backward; we were already
+         at the first line. */
+      
+      /* We leave real->line as-is */
+
+      /* Note that we didn't clamp to the start of the first line. */
+      
+      return FALSE;
+    }
+}
+
 /* The return value indicates (MOVEMENT OCCURRED && NEW ITER IS
  * DEREFERENCEABLE)
  */
 static gboolean
-forward_char(GtkTextRealIter *real)
+forward_char (GtkTextRealIter *real)
 {
   GtkTextIter *iter = (GtkTextIter*)real;
 
@@ -1465,7 +1519,7 @@ forward_char(GtkTextRealIter *real)
       /* Just moving within a segment. Keep byte count
          up-to-date, if it was already up-to-date. */
 
-      g_assert(real->segment->type == &gtk_text_char_type);
+      g_assert (real->segment->type == &gtk_text_char_type);
       
       if (real->line_byte_offset >= 0)
         {
@@ -1502,7 +1556,7 @@ forward_char(GtkTextRealIter *real)
 }
 
 gboolean
-gtk_text_iter_forward_indexable_segment(GtkTextIter *iter)
+gtk_text_iter_forward_indexable_segment (GtkTextIter *iter)
 {
   /* Need to move to the next segment; if no next segment,
      need to move to next line. */
@@ -1524,7 +1578,7 @@ gtk_text_iter_forward_indexable_segment(GtkTextIter *iter)
   if (real->line_char_offset >= 0)
     {
       chars_skipped = real->segment->char_count - real->segment_char_offset;
-      g_assert(chars_skipped > 0);
+      g_assert (chars_skipped > 0);
     }
   else
     chars_skipped = 0;
@@ -1532,7 +1586,7 @@ gtk_text_iter_forward_indexable_segment(GtkTextIter *iter)
   if (real->line_byte_offset >= 0)
     {
       bytes_skipped = real->segment->byte_count - real->segment_byte_offset;
-      g_assert(bytes_skipped > 0);
+      g_assert (bytes_skipped > 0);
     }
   else
     bytes_skipped = 0;
@@ -1551,17 +1605,17 @@ gtk_text_iter_forward_indexable_segment(GtkTextIter *iter)
 
       if (real->line_byte_offset >= 0)
         {
-          g_assert(bytes_skipped > 0);
+          g_assert (bytes_skipped > 0);
           real->segment_byte_offset = 0;
           real->line_byte_offset += bytes_skipped;
         }
 
       if (real->line_char_offset >= 0)
         {
-          g_assert(chars_skipped > 0);
+          g_assert (chars_skipped > 0);
           real->segment_char_offset = 0;
           real->line_char_offset += chars_skipped;
-          adjust_char_index(real, chars_skipped);
+          adjust_char_index (real, chars_skipped);
         }
 
       check_invariants(iter);
@@ -1571,21 +1625,19 @@ gtk_text_iter_forward_indexable_segment(GtkTextIter *iter)
   else
     {      
       /* End of the line */
-      if (forward_line_leaving_caches_unmodified(real))
+      if (forward_line_leaving_caches_unmodified (real))
         {
-          adjust_line_number(real, 1);
+          adjust_line_number (real, 1);
           if (real->line_char_offset >= 0)
-            adjust_char_index(real, chars_skipped);
-
-          check_invariants(iter);
+            adjust_char_index (real, chars_skipped);
 
-          g_assert(real->line_byte_offset == 0);
-          g_assert(real->line_char_offset == 0);
-          g_assert(real->segment_byte_offset == 0);
-          g_assert(real->segment_char_offset == 0);
-          g_assert(gtk_text_iter_starts_line(iter));
+          g_assert (real->line_byte_offset == 0);
+          g_assert (real->line_char_offset == 0);
+          g_assert (real->segment_byte_offset == 0);
+          g_assert (real->segment_char_offset == 0);
+          g_assert (gtk_text_iter_starts_line(iter));
 
-          check_invariants(iter);
+          check_invariants (iter);
 
           if (gtk_text_iter_is_last (iter))
             return FALSE;
@@ -1603,13 +1655,156 @@ gtk_text_iter_forward_indexable_segment(GtkTextIter *iter)
     }
 }
 
+static gboolean
+at_last_indexable_segment (GtkTextRealIter *real)
+{
+  GtkTextLineSegment *seg;
+
+  /* Return TRUE if there are no indexable segments after
+   * this iterator.
+   */
+  
+  seg = real->segment->next;
+  while (seg)
+    {
+      if (seg->char_count > 0)
+        return FALSE;
+      seg = seg->next;
+    }
+  return TRUE;
+}
+
+/* Goes back to the start of the next segment, even if
+ * we're not at the start of the current segment (always
+ * ends up on a different segment if it returns TRUE)
+ */
 gboolean
-gtk_text_iter_backward_indexable_segment(GtkTextIter *iter)
+gtk_text_iter_backward_indexable_segment (GtkTextIter *iter)
 {
-  g_warning("FIXME");
+  /* Move to the start of the previous segment; if no previous
+   * segment, to the last segment in the previous line. This is
+   * inherently a bit inefficient due to the singly-linked list and
+   * tree nodes, but we can't afford the RAM for doubly-linked.
+   */
+  GtkTextRealIter *real;
+  GtkTextLineSegment *seg;
+  GtkTextLineSegment *any_seg;
+  GtkTextLineSegment *prev_seg;
+  GtkTextLineSegment *prev_any_seg;
+  gint bytes_skipped;
+  gint chars_skipped;
+  
+  g_return_val_if_fail (iter != NULL, FALSE);
 
+  real = gtk_text_iter_make_real(iter);
 
-  return FALSE;
+  if (real == NULL)
+    return FALSE;
+
+  check_invariants (iter);
+  
+  /* Find first segments in line */
+  any_seg = real->line->segments;
+  seg = any_seg;
+  while (seg->char_count == 0)
+    seg = seg->next;
+
+  if (seg == real->segment)
+    {
+      /* Could probably do this case faster by hand-coding the
+       * iteration.
+       */
+      
+      /* We were already at the start of a line;
+       * go back to the previous line.
+       */
+      if (gtk_text_iter_backward_line (iter))
+        {
+          /* Go forward to last indexable segment in line. */
+          while (!at_last_indexable_segment (real))
+            gtk_text_iter_forward_indexable_segment (iter);            
+
+          check_invariants (iter);
+
+          return TRUE;
+        }
+      else
+        return FALSE; /* We were at the start of the first line. */
+    }
+
+  /* We must be in the middle of a line; so find the indexable
+   * segment just before our current segment.
+   */
+  g_assert (seg != real->segment);
+  while (seg != real->segment)
+    {
+      prev_seg = seg;
+      prev_any_seg = any_seg;
+      
+      any_seg = seg->next;
+      seg = any_seg;
+      while (seg->char_count == 0)
+        seg = seg->next;
+    }
+
+  g_assert (prev_seg != NULL);
+  g_assert (prev_any_seg != NULL);
+  g_assert (prev_seg->char_count > 0);
+  
+  /* We skipped the entire previous segment, plus any
+   * chars we were into the current segment.
+   */
+  if (real->segment_byte_offset >= 0)
+    bytes_skipped = prev_seg->byte_count + real->segment_byte_offset;
+  else
+    bytes_skipped = -1;
+
+  if (real->segment_char_offset >= 0)
+    chars_skipped = prev_seg->char_count + real->segment_char_offset;
+  else
+    chars_skipped = -1;
+  
+  real->segment = prev_seg;
+  real->any_segment = prev_any_seg;
+  real->segment_byte_offset = 0;
+  real->segment_char_offset = 0;
+
+  if (bytes_skipped >= 0)
+    {
+      if (real->line_byte_offset >= 0)
+        {
+          real->line_byte_offset -= bytes_skipped;
+          g_assert (real->line_byte_offset >= 0);
+        }
+    }
+  else
+    real->line_byte_offset = -1;
+  
+  if (chars_skipped >= 0)
+    {
+      if (real->line_char_offset >= 0)
+        {
+          real->line_char_offset -= chars_skipped;
+          g_assert (real->line_char_offset >= 0);
+        }
+      
+      if (real->cached_char_index >= 0)
+        {
+          real->cached_char_index -= chars_skipped;
+          g_assert (real->cached_char_index >= 0);
+        }
+    }
+  else
+    {
+      real->line_char_offset = -1;
+      real->cached_char_index = -1;
+    }
+
+  /* line number is unchanged. */
+
+  check_invariants (iter);
+  
+  return TRUE;
 }
 
 /**
@@ -1677,7 +1872,7 @@ gtk_text_iter_prev_char(GtkTextIter *iter)
 
   I guess you'd have to profile the various approaches.
 */
-#define MAX_LINEAR_SCAN 300
+#define MAX_LINEAR_SCAN 150
 
 
 /**
@@ -1822,23 +2017,34 @@ gtk_text_iter_backward_chars(GtkTextIter *iter, gint count)
   else
     {
       /* We need to go back into previous segments. For now,
-         just keep this really simple. */
-      gint current_char_index;
-      gint new_char_index;
+       * just keep this really simple. FIXME
+       * use backward_indexable_segment.
+       */
+      if (TRUE || count > MAX_LINEAR_SCAN)
+        {
+          gint current_char_index;
+          gint new_char_index;
       
-      current_char_index = gtk_text_iter_get_offset(iter);
+          current_char_index = gtk_text_iter_get_offset (iter);
 
-      if (current_char_index == 0)
-        return FALSE; /* can't move backward */
+          if (current_char_index == 0)
+            return FALSE; /* can't move backward */
       
-      new_char_index = current_char_index - count;
-      if (new_char_index < 0)
-        new_char_index = 0;
-      gtk_text_iter_set_offset(iter, new_char_index);
+          new_char_index = current_char_index - count;
+          if (new_char_index < 0)
+            new_char_index = 0;
+          gtk_text_iter_set_offset (iter, new_char_index);
 
-      check_invariants(iter);
+          check_invariants(iter);
       
-      return TRUE;
+          return TRUE;
+        }
+      else
+        {
+          /* FIXME backward_indexable_segment here */
+          
+          return FALSE;
+        }
     }
 }
 
@@ -1935,7 +2141,7 @@ gtk_text_iter_backward_line(GtkTextIter *iter)
         return FALSE;
     }
 
-  invalidate_char_index(real);
+  invalidate_char_index (real);
   
   real->line_byte_offset = 0;
   real->line_char_offset = 0;
@@ -1946,7 +2152,7 @@ gtk_text_iter_backward_line(GtkTextIter *iter)
   /* Find first segment in line */
   real->any_segment = real->line->segments;
   real->segment = gtk_text_line_byte_to_segment(real->line,
-                                                 0, &offset);
+                                                0, &offset);
 
   g_assert(offset == 0);
 
@@ -2207,18 +2413,18 @@ gtk_text_iter_set_line_offset(GtkTextIter *iter,
 {
   GtkTextRealIter *real;
   
-  g_return_if_fail(iter != NULL);
+  g_return_if_fail (iter != NULL);
   
-  real = gtk_text_iter_make_surreal(iter);
+  real = gtk_text_iter_make_surreal (iter);
 
   if (real == NULL)
     return;
 
-  check_invariants(iter);
+  check_invariants (iter);
   
-  iter_set_from_char_offset(real, real->line, char_on_line);
+  iter_set_from_char_offset (real, real->line, char_on_line);
 
-  check_invariants(iter);
+  check_invariants (iter);
 }
 
 void
@@ -2228,23 +2434,23 @@ gtk_text_iter_set_line(GtkTextIter *iter, gint line_number)
   gint real_line;
   GtkTextRealIter *real;
   
-  g_return_if_fail(iter != NULL);
+  g_return_if_fail (iter != NULL);
   
-  real = gtk_text_iter_make_surreal(iter);
+  real = gtk_text_iter_make_surreal (iter);
 
   if (real == NULL)
     return;
 
-  check_invariants(iter);
+  check_invariants (iter);
   
-  line = gtk_text_btree_get_line(real->tree, line_number, &real_line);
+  line = gtk_text_btree_get_line (real->tree, line_number, &real_line);
 
-  iter_set_from_char_offset(real, line, 0);
+  iter_set_from_char_offset (real, line, 0);
   
   /* We might as well cache this, since we know it. */
   real->cached_line_number = real_line;
 
-  check_invariants(iter);
+  check_invariants (iter);
 }
 
 void
@@ -2360,10 +2566,10 @@ gtk_text_iter_forward_to_tag_toggle (GtkTextIter *iter,
   check_invariants(iter);
   
   current_line = real->line;
-  next_line = gtk_text_line_next_could_contain_tag(current_line,
-                                                   real->tree, tag);
+  next_line = gtk_text_line_next_could_contain_tag (current_line,
+                                                    real->tree, tag);
   
-  while (gtk_text_iter_forward_indexable_segment(iter))
+  while (gtk_text_iter_forward_indexable_segment (iter))
     {
       /* If we went forward to a line that couldn't contain a toggle
          for the tag, then skip forward to a line that could contain
@@ -2374,15 +2580,15 @@ gtk_text_iter_forward_to_tag_toggle (GtkTextIter *iter,
           if (next_line == NULL)
             {
               /* End of search. Set to end of buffer. */
-              gtk_text_btree_get_last_iter(real->tree, iter);
+              gtk_text_btree_get_last_iter (real->tree, iter);
               return FALSE;
             }
               
           if (real->line != next_line)
-            iter_set_from_byte_offset(real, next_line, 0);
+            iter_set_from_byte_offset (real, next_line, 0);
 
           current_line = real->line;
-          next_line = gtk_text_line_next_could_contain_tag(current_line,
+          next_line = gtk_text_line_next_could_contain_tag (current_line,
                                                             real->tree,
                                                             tag);
         }
@@ -2397,7 +2603,7 @@ gtk_text_iter_forward_to_tag_toggle (GtkTextIter *iter,
     }
 
   /* Check end iterator for tags */
-  if (gtk_text_iter_toggles_tag(iter, tag))
+  if (gtk_text_iter_toggles_tag (iter, tag))
     {
       /* If there's a toggle here, it isn't indexable so
          any_segment can't be the indexable segment. */
@@ -2409,12 +2615,103 @@ gtk_text_iter_forward_to_tag_toggle (GtkTextIter *iter,
   return FALSE;
 }
 
+/**
+ * gtk_text_iter_backward_to_tag_toggle:
+ * @iter: a #GtkTextIter
+ * @tag: a #GtkTextTag, or NULL
+ * 
+ * Moves backward to the next toggle (on or off) of the
+ * #GtkTextTag @tag, or to the next toggle of any tag if
+ * @tag is NULL. If no matching tag toggles are found,
+ * returns FALSE, otherwise TRUE. Does not return toggles
+ * located at @iter, only toggles before @iter.
+ * 
+ * Return value: whether we found a tag toggle before @iter
+ **/
 gboolean
 gtk_text_iter_backward_to_tag_toggle (GtkTextIter *iter,
                                       GtkTextTag  *tag)
 {
+  GtkTextLine *prev_line;
+  GtkTextLine *current_line;
+  GtkTextRealIter *real;
+
+  g_return_val_if_fail(iter != NULL, FALSE);
+  
+  real = gtk_text_iter_make_real(iter);
+
+  if (real == NULL)
+    return FALSE;
 
-  g_warning("FIXME");
+  check_invariants(iter);
+  
+  current_line = real->line;
+  prev_line = gtk_text_line_previous_could_contain_tag (current_line,
+                                                        real->tree, tag);
+  
+
+  /* If we're at segment start, go to the previous segment;
+   * if mid-segment, snap to start of current segment.
+   */
+  if (is_segment_start (real))
+    {
+      if (!gtk_text_iter_backward_indexable_segment (iter))
+        return FALSE;
+    }
+  else
+    {
+      ensure_char_offsets (real);
+      
+      if (!gtk_text_iter_backward_chars (iter, real->segment_char_offset))
+        return FALSE;
+    }
+  
+  do
+    {
+      /* If we went backward to a line that couldn't contain a toggle
+       * for the tag, then skip backward further to a line that
+       * could contain it. This potentially skips huge hunks of the
+       * tree, so we aren't a purely linear search.
+       */
+      if (real->line != current_line)
+        {
+          if (prev_line == NULL)
+            {
+              /* End of search. Set to start of buffer. */
+              gtk_text_btree_get_iter_at_char (real->tree, iter, 0);
+              return FALSE;
+            }
+              
+          if (real->line != prev_line)
+            {
+              /* Set to last segment in prev_line (could do this
+               * more quickly)
+               */
+              iter_set_from_byte_offset (real, prev_line, 0);
+              
+              while (!at_last_indexable_segment (real))
+                gtk_text_iter_forward_indexable_segment (iter);            
+            }
+          
+          current_line = real->line;
+          prev_line = gtk_text_line_previous_could_contain_tag (current_line,
+                                                                real->tree,
+                                                                tag);
+        }
+
+      if (gtk_text_iter_toggles_tag (iter, tag))
+        {
+          /* If there's a toggle here, it isn't indexable so
+           * any_segment can't be the indexable segment.
+           */
+          g_assert (real->any_segment != real->segment);
+          return TRUE;
+        }
+    }
+  while (gtk_text_iter_backward_indexable_segment (iter));    
+  
+  /* Reached front of buffer */
+  return FALSE;
 }
 
 static gboolean
@@ -2437,9 +2734,9 @@ gtk_text_iter_forward_find_char (GtkTextIter *iter,
   g_return_val_if_fail(iter != NULL, FALSE);
   g_return_val_if_fail(pred != NULL, FALSE);
 
-  while (gtk_text_iter_next_char(iter))
+  while (gtk_text_iter_next_char (iter))
     {
-      if (matches_pred(iter, pred, user_data))
+      if (matches_pred (iter, pred, user_data))
         return TRUE;
     }
   
@@ -2454,9 +2751,9 @@ gtk_text_iter_backward_find_char (GtkTextIter *iter,
   g_return_val_if_fail(iter != NULL, FALSE);
   g_return_val_if_fail(pred != NULL, FALSE);
 
-  while (gtk_text_iter_prev_char(iter))
+  while (gtk_text_iter_prev_char (iter))
     {
-      if (matches_pred(iter, pred, user_data))
+      if (matches_pred (iter, pred, user_data))
         return TRUE;
     }
   
@@ -2640,7 +2937,8 @@ gtk_text_iter_forward_search (GtkTextIter *iter,
        * gtk_text_iter_get_text() is called repeatedly on
        * a single line.
        */
-      if (lines_match (&search, (const gchar**)lines, visible_only, slice, &match))
+      if (lines_match (&search, (const gchar**)lines,
+                       visible_only, slice, &match))
         {
           retval = TRUE;
           
@@ -3062,8 +3360,8 @@ gtk_text_iter_check (const GtkTextIter *iter)
   if (real->line_byte_offset >= 0)
     {
       gtk_text_line_byte_locate(real->line, real->line_byte_offset,
-                                 &byte_segment, &byte_any_segment,
-                                 &seg_byte_offset, &line_byte_offset);
+                                &byte_segment, &byte_any_segment,
+                                &seg_byte_offset, &line_byte_offset);
 
       if (line_byte_offset != real->line_byte_offset)
         g_error("wrong byte offset was stored in iterator");
@@ -3084,8 +3382,8 @@ gtk_text_iter_check (const GtkTextIter *iter)
   if (real->line_char_offset >= 0)
     {
       gtk_text_line_char_locate(real->line, real->line_char_offset,
-                                 &char_segment, &char_any_segment,
-                                 &seg_char_offset, &line_char_offset);
+                                &char_segment, &char_any_segment,
+                                &seg_char_offset, &line_char_offset);
 
       if (line_char_offset != real->line_char_offset)
         g_error("wrong char offset was stored in iterator");
index 0d9104d3e16d445fdb0dfc1830df6e98ac189eb9..bd6835a987c94dc7625dd27f72616ebc6b61ca68 100644 (file)
@@ -10,18 +10,15 @@ extern "C" {
 #include <gtk/gtktextiter.h>
 #include <gtk/gtktextbtree.h>
 
-GtkTextLineSegment *gtk_text_iter_get_indexable_segment(const GtkTextIter *iter);
-GtkTextLineSegment *gtk_text_iter_get_any_segment(const GtkTextIter *iter);
+GtkTextLineSegment *gtk_text_iter_get_indexable_segment      (const GtkTextIter *iter);
+GtkTextLineSegment *gtk_text_iter_get_any_segment            (const GtkTextIter *iter);
+GtkTextLine *       gtk_text_iter_get_text_line              (const GtkTextIter *iter);
+GtkTextBTree *      gtk_text_iter_get_btree                  (const GtkTextIter *iter);
+gboolean            gtk_text_iter_forward_indexable_segment  (GtkTextIter       *iter);
+gboolean            gtk_text_iter_backward_indexable_segment (GtkTextIter       *iter);
+gint                gtk_text_iter_get_segment_byte           (const GtkTextIter *iter);
+gint                gtk_text_iter_get_segment_char           (const GtkTextIter *iter);
 
-GtkTextLine *gtk_text_iter_get_text_line(const GtkTextIter *iter);
-
-GtkTextBTree *gtk_text_iter_get_btree(const GtkTextIter *iter);
-
-gboolean gtk_text_iter_forward_indexable_segment(GtkTextIter *iter);
-gboolean gtk_text_iter_backward_indexable_segment(GtkTextIter *iter);
-
-gint gtk_text_iter_get_segment_byte(const GtkTextIter *iter);
-gint gtk_text_iter_get_segment_char(const GtkTextIter *iter);
 
 /* debug */
 void gtk_text_iter_check(const GtkTextIter *iter);
index ff7da5e3b0214e45d29b561525d1a54d72fd04f6..d4b57f973a66f4ba8b341b5f9c2af9f4306df2fc 100644 (file)
@@ -1204,8 +1204,6 @@ add_cursor (GtkTextLayout      *layout,
            GtkTextLineSegment *seg,
            gint                start)
 {
-  GtkTextIter selection_start, selection_end;
-  
   PangoRectangle strong_pos, weak_pos;
   GtkTextCursorDisplay *cursor;
 
@@ -1214,7 +1212,8 @@ add_cursor (GtkTextLayout      *layout,
    */
   if (gtk_text_btree_mark_is_insert (_gtk_text_buffer_get_btree (layout->buffer),
                                      (GtkTextMark*)seg) &&
-      (!layout->cursor_visible || gtk_text_buffer_get_selection_bounds (layout->buffer, &selection_start, &selection_end)))
+      (!layout->cursor_visible ||
+       gtk_text_buffer_get_selection_bounds (layout->buffer, NULL, NULL)))
     return;
   
   pango_layout_get_cursor_pos (display->layout, start, &strong_pos, &weak_pos);
@@ -1429,7 +1428,8 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout,
   tmp_list2 = cursor_segs;
   while (tmp_list1)
     {
-      add_cursor (layout, display, tmp_list2->data, GPOINTER_TO_INT (tmp_list1->data));
+      add_cursor (layout, display, tmp_list2->data,
+                  GPOINTER_TO_INT (tmp_list1->data));
       tmp_list1 = tmp_list1->next;
       tmp_list2 = tmp_list2->next;
     }
index 5d812f1a994e5f7ca4e71cbdc1e184b172d57024..61ad83dbf7b5ff64426d939569c1476089c1bd82 100644 (file)
@@ -146,7 +146,7 @@ struct _GtkTextLineDisplay
   gint bottom_margin;
 
   gboolean size_only;
- GtkTextLine *line;
 GtkTextLine *line;
 };
 
 extern PangoAttrType gtk_text_attr_appearance_type;
index 4bbd655b6415460278f871a62e83f9990972951b..6e5b6610841381328f3fe6929cbdaa08faa52c9a 100644 (file)
@@ -530,7 +530,7 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
                                "delete", 2,
                                GTK_TYPE_ENUM, GTK_DELETE_CHARS,
                                GTK_TYPE_INT, 1);
-
+  
   gtk_binding_entry_add_signal (binding_set, GDK_d, GDK_CONTROL_MASK,
                                "delete", 2,
                                GTK_TYPE_ENUM, GTK_DELETE_CHARS,
@@ -2590,6 +2590,16 @@ gtk_text_view_delete (GtkTextView   *text_view,
       break;
 
     case GTK_DELETE_PARAGRAPH_ENDS:
+      /* If we're already at a newline, we need to
+       * simply delete that newline, instead of
+       * moving to the next one.
+       */
+      if (gtk_text_iter_get_char (&end) == '\n')
+        {
+          gtk_text_iter_next_char (&end);
+          --count;
+        }
+
       while (count > 0)
         {
           if (!gtk_text_iter_forward_to_newline (&end))
@@ -3058,9 +3068,9 @@ gtk_text_view_destroy_layout (GtkTextView *text_view)
  */
 
 static void
-gtk_text_view_start_selection_dnd (GtkTextView *text_view,
+gtk_text_view_start_selection_dnd (GtkTextView       *text_view,
                                    const GtkTextIter *iter,
-                                   GdkEventMotion *event)
+                                   GdkEventMotion    *event)
 {
   GdkDragContext *context;
   GtkTargetList *target_list;
@@ -3079,7 +3089,8 @@ gtk_text_view_start_selection_dnd (GtkTextView *text_view,
   gtk_drag_set_icon_default (context);
 
   /* We're inside the selection, so start without being able
-     to accept the drag. */
+   * to accept the drag.
+   */
   gdk_drag_status (context, 0, event->time);
   gtk_text_mark_set_visible (text_view->dnd_mark, FALSE);
 }
@@ -3201,7 +3212,8 @@ gtk_text_view_drag_motion (GtkWidget        *widget,
     {
       if (gtk_text_iter_editable (&newplace, text_view->editable))
         {
-          gtk_text_mark_set_visible (text_view->dnd_mark, text_view->cursor_visible);
+          gtk_text_mark_set_visible (text_view->dnd_mark,
+                                     text_view->cursor_visible);
           
           gdk_drag_status (context, context->suggested_action, time);
         }
index 286f9b5fd7272384a6f53dfb4869d4b6bdf257af..437be8832f9c69c727088e786daf52be666818d9 100644 (file)
@@ -16,14 +16,14 @@ main (int argc, char** argv)
   int n;
   gunichar ch;
   GtkTextIter start, end;
-  
+
   gtk_init (&argc, &argv);
 
   /* Check UTF8 unknown char thing */
   g_assert (g_utf8_strlen (gtk_text_unknown_char_utf8, 3) == 1);
   ch = g_utf8_get_char (gtk_text_unknown_char_utf8);
   g_assert (ch == gtk_text_unknown_char);
-  
+
   /* First, we turn on btree debugging. */
   gtk_debug_flags |= GTK_DEBUG_TEXT;
 
@@ -31,18 +31,18 @@ main (int argc, char** argv)
   buffer = gtk_text_buffer_new (NULL);
 
   /* Check that buffer starts with one empty line and zero chars */
-  
+
   n = gtk_text_buffer_get_line_count (buffer);
   if (n != 1)
     g_error ("%d lines, expected 1", n);
-  
+
   n = gtk_text_buffer_get_char_count (buffer);
   if (n != 1)
     g_error ("%d chars, expected 1", n);
-  
+
   /* Run gruesome alien test suite on buffer */
   run_tests (buffer);
-  
+
   /* Put stuff in the buffer */
 
   fill_buffer (buffer);
@@ -59,7 +59,7 @@ main (int argc, char** argv)
   n = gtk_text_buffer_get_line_count (buffer);
   if (n != 1)
     g_error ("%d lines, expected 1", n);
-  
+
   n = gtk_text_buffer_get_char_count (buffer);
   if (n != 1)
     g_error ("%d chars, expected 1", n);
@@ -67,10 +67,165 @@ main (int argc, char** argv)
   run_tests (buffer);
 
   g_print ("All tests passed.\n");
-  
+
   return 0;
 }
 
+static gint
+count_toggles_at_iter (GtkTextIter *iter,
+                       GtkTextTag  *of_tag)
+{
+  GSList *tags;
+  GSList *tmp;
+  gint count = 0;
+  
+  /* get toggle-ons and toggle-offs */
+  tags = gtk_text_iter_get_toggled_tags (iter, TRUE);
+  tags = g_slist_concat (tags,
+                         gtk_text_iter_get_toggled_tags (iter, FALSE));
+  
+  tmp = tags;
+  while (tmp != NULL)
+    {
+      if (of_tag == NULL)
+        ++count;
+      else if (of_tag == tmp->data)
+        ++count;
+      
+      tmp = g_slist_next (tmp);
+    }
+  
+  g_slist_free (tags);
+
+  return count;
+}
+     
+static gint
+count_toggles_in_buffer (GtkTextBuffer *buffer,
+                         GtkTextTag    *of_tag)
+{
+  GtkTextIter iter;
+  gint count = 0;
+  
+  gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
+  do
+    {
+      count += count_toggles_at_iter (&iter, of_tag);
+    }
+  while (gtk_text_iter_next_char (&iter));
+
+  /* Do the end iterator, because forward_char won't return TRUE
+   * on it.
+   */
+  count += count_toggles_at_iter (&iter, of_tag);
+  
+  return count;
+}
+
+static void
+check_specific_tag (GtkTextBuffer *buffer,
+                    const gchar   *tag_name)
+{
+  GtkTextIter iter;
+  GtkTextTag *tag;
+  gboolean state;
+  gint count;
+  gint buffer_count;
+  gint last_offset;
+  
+  tag = gtk_text_tag_table_lookup (gtk_text_buffer_get_tag_table (buffer),
+                                   tag_name);
+
+  buffer_count = count_toggles_in_buffer (buffer, tag);
+  
+  state = FALSE;
+  count = 0;
+
+  last_offset = -1;
+  gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
+  if (gtk_text_iter_toggles_tag (&iter, tag) ||
+      gtk_text_iter_forward_to_tag_toggle (&iter, tag))
+    {
+      do
+        {
+          gint this_offset;
+          
+          ++count;
+
+          this_offset = gtk_text_iter_get_offset (&iter);
+
+          if (this_offset <= last_offset)
+            g_error ("forward_to_tag_toggle moved in wrong direction");
+
+          last_offset = this_offset;
+          
+          if (gtk_text_iter_begins_tag (&iter, tag))
+            {
+              if (state)
+                g_error ("Tag %p is already on, and was toggled on?", tag);
+              state = TRUE;
+            }          
+          else if (gtk_text_iter_ends_tag (&iter, tag))
+            {
+              if (!state)
+                g_error ("Tag %p toggled off, but wasn't toggled on?", tag);
+              state = FALSE;
+            }
+          else
+            g_error ("forward_to_tag_toggle went to a location without a toggle");
+        }
+      while (gtk_text_iter_forward_to_tag_toggle (&iter, tag));
+    }
+
+  if (count != buffer_count)
+    g_error ("Counted %d tags iterating by char, %d iterating by tag toggle\n",
+             buffer_count, count);
+  
+  state = FALSE;
+  count = 0;
+  
+  gtk_text_buffer_get_last_iter (buffer, &iter);
+  last_offset = gtk_text_iter_get_offset (&iter);
+  if (gtk_text_iter_toggles_tag (&iter, tag) ||
+      gtk_text_iter_backward_to_tag_toggle (&iter, tag))
+    {
+      do
+        {
+          gint this_offset;
+          
+          ++count;
+
+          this_offset = gtk_text_iter_get_offset (&iter);
+          
+          if (this_offset >= last_offset)
+            g_error ("backward_to_tag_toggle moved in wrong direction");
+          
+          last_offset = this_offset;
+
+          if (gtk_text_iter_begins_tag (&iter, tag))
+            {
+              if (!state)
+                g_error ("Tag %p wasn't on when we got to the on toggle going backward?", tag);
+              state = FALSE;
+            }
+          else if (gtk_text_iter_ends_tag (&iter, tag))
+            {
+              if (state)
+                g_error ("Tag %p off toggle, but we were already inside a tag?", tag);
+              state = TRUE;
+            }
+          else
+            g_error ("backward_to_tag_toggle went to a location without a toggle");
+        }
+      while (gtk_text_iter_backward_to_tag_toggle (&iter, tag));
+    }
+
+  if (count != buffer_count)
+    g_error ("Counted %d tags iterating by char, %d iterating by tag toggle\n",
+             buffer_count, count);
+
+}
+
 static void
 run_tests (GtkTextBuffer *buffer)
 {
@@ -81,9 +236,13 @@ run_tests (GtkTextBuffer *buffer)
   gint i, j;
   gint num_chars;
   GtkTextMark *bar_mark;
+  GtkTextTag *tag;
+  GHashTable *tag_states;
+  gint count;
+  gint buffer_count;
   
   gtk_text_buffer_get_bounds (buffer, &start, &end);
-  
+
   /* Check that walking the tree via chars and via iterators produces
    * the same number of indexable locations.
    */
@@ -95,7 +254,7 @@ run_tests (GtkTextBuffer *buffer)
     {
       GtkTextIter current;
       GtkTextMark *foo_mark;
-      
+
       gtk_text_buffer_get_iter_at_offset (buffer, &current, i);
 
       if (!gtk_text_iter_equal (&iter, &current))
@@ -119,11 +278,11 @@ run_tests (GtkTextBuffer *buffer)
           gtk_text_iter_spew (&mark, "mark");
           g_error ("Mark not moved to the right place.");
         }
-      
+
       foo_mark = gtk_text_buffer_create_mark (buffer, "foo", &iter, FALSE);
       gtk_text_buffer_get_iter_at_mark (buffer, &mark, foo_mark);
       gtk_text_buffer_delete_mark (buffer, foo_mark);
-      
+
       if (!gtk_text_iter_equal (&iter, &mark))
         {
           gtk_text_iter_spew (&iter, "iter");
@@ -134,34 +293,34 @@ run_tests (GtkTextBuffer *buffer)
       if (gtk_text_iter_is_last (&iter))
         g_error ("iterators ran out before chars (offset %d of %d)",
                  i, num_chars);
-      
+
       gtk_text_iter_next_char (&iter);
 
       gtk_text_buffer_move_mark (buffer, bar_mark, &iter);
-      
+
       ++i;
     }
 
   if (!gtk_text_iter_equal (&iter, &end))
     g_error ("Iterating over all chars didn't end with the end iter");
 
-  /* Do the tree-walk backward 
+  /* Do the tree-walk backward
    */
   num_chars = gtk_text_buffer_get_char_count (buffer);
   gtk_text_buffer_get_iter_at_offset (buffer, &iter, -1);
 
   gtk_text_buffer_move_mark (buffer, bar_mark, &iter);
-  
+
   i = num_chars;
 
   if (!gtk_text_iter_equal (&iter, &end))
     g_error ("iter at char -1 is not equal to the end iterator");
-  
+
   while (i >= 0)
     {
       GtkTextIter current;
       GtkTextMark *foo_mark;
-      
+
       gtk_text_buffer_get_iter_at_offset (buffer, &current, i);
 
       if (!gtk_text_iter_equal (&iter, &current))
@@ -184,18 +343,18 @@ run_tests (GtkTextBuffer *buffer)
           gtk_text_iter_spew (&mark, "mark");
           g_error ("Mark not moved to the right place.");
         }
-      
+
       foo_mark = gtk_text_buffer_create_mark (buffer, "foo", &iter, FALSE);
       gtk_text_buffer_get_iter_at_mark (buffer, &mark, foo_mark);
       gtk_text_buffer_delete_mark (buffer, foo_mark);
-      
+
       if (!gtk_text_iter_equal (&iter, &mark))
         {
           gtk_text_iter_spew (&iter, "iter");
           gtk_text_iter_spew (&mark, "mark");
           g_error ("Mark not created in the right place.");
         }
-      
+
       if (i > 0)
         {
           if (!gtk_text_iter_prev_char (&iter))
@@ -208,10 +367,10 @@ run_tests (GtkTextBuffer *buffer)
           if (gtk_text_iter_prev_char (&iter))
             g_error ("went backward from 0?");
         }
-      
+
       --i;
     }
-  
+
   if (!gtk_text_iter_equal (&iter, &start))
     g_error ("Iterating backward over all chars didn't end with the start iter");
 
@@ -223,10 +382,169 @@ run_tests (GtkTextBuffer *buffer)
   gtk_text_buffer_get_iter_at_line (buffer, &iter, 0);
   while (gtk_text_iter_forward_line (&iter))
     ++i;
-  
+
   if (i != gtk_text_buffer_get_line_count (buffer))
     g_error ("Counted %d lines, buffer has %d", i,
              gtk_text_buffer_get_line_count (buffer));
+
+  /*
+   * Check that moving over tag toggles thinks about working.
+   */
+
+  buffer_count = count_toggles_in_buffer (buffer, NULL);
+  
+  tag_states = g_hash_table_new (NULL, NULL);
+  count = 0;
+  
+  gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
+  if (gtk_text_iter_toggles_tag (&iter, NULL) ||
+      gtk_text_iter_forward_to_tag_toggle (&iter, NULL))
+    {
+      do
+        {
+          GSList *tags;
+          GSList *tmp;
+          gboolean found_some = FALSE;
+          
+          /* get toggled-on tags */
+          tags = gtk_text_iter_get_toggled_tags (&iter, TRUE);
+
+          if (tags)
+            found_some = TRUE;
+          
+          tmp = tags;
+          while (tmp != NULL)
+            {
+              ++count;
+              
+              tag = tmp->data;
+              
+              if (g_hash_table_lookup (tag_states, tag))
+                g_error ("Tag %p is already on, and was toggled on?", tag);
+
+              g_hash_table_insert (tag_states, tag, GINT_TO_POINTER (TRUE));
+          
+              tmp = g_slist_next (tmp);
+            }
+
+          g_slist_free (tags);
+      
+          /* get toggled-off tags */
+          tags = gtk_text_iter_get_toggled_tags (&iter, FALSE);
+
+          if (tags)
+            found_some = TRUE;
+          
+          tmp = tags;
+          while (tmp != NULL)
+            {
+              ++count;
+              
+              tag = tmp->data;
+
+              if (!g_hash_table_lookup (tag_states, tag))
+                g_error ("Tag %p is already off, and was toggled off?", tag);
+
+              g_hash_table_remove (tag_states, tag);
+          
+              tmp = g_slist_next (tmp);
+            }
+
+          g_slist_free (tags);
+
+          if (!found_some)
+            g_error ("No tags found going forward to tag toggle.");
+
+        }
+      while (gtk_text_iter_forward_to_tag_toggle (&iter, NULL));
+    }
+  
+  g_hash_table_destroy (tag_states);
+
+  if (count != buffer_count)
+    g_error ("Counted %d tags iterating by char, %d iterating by tag toggle\n",
+             buffer_count, count);
+  
+  /* Go backward; here TRUE in the hash means we saw
+   * an off toggle last.
+   */
+  
+  tag_states = g_hash_table_new (NULL, NULL);
+  count = 0;
+  
+  gtk_text_buffer_get_last_iter (buffer, &iter);
+  if (gtk_text_iter_toggles_tag (&iter, NULL) ||
+      gtk_text_iter_backward_to_tag_toggle (&iter, NULL))
+    {
+      do
+        {
+          GSList *tags;
+          GSList *tmp;
+          gboolean found_some = FALSE;
+          
+          /* get toggled-off tags */
+          tags = gtk_text_iter_get_toggled_tags (&iter, FALSE);
+
+          if (tags)
+            found_some = TRUE;
+          
+          tmp = tags;
+          while (tmp != NULL)
+            {
+              ++count;
+              
+              tag = tmp->data;
+
+              if (g_hash_table_lookup (tag_states, tag))
+                g_error ("Tag %p has two off-toggles in a row?", tag);
+          
+              g_hash_table_insert (tag_states, tag, GINT_TO_POINTER (TRUE));
+          
+              tmp = g_slist_next (tmp);
+            }
+
+          g_slist_free (tags);
+      
+          /* get toggled-on tags */
+          tags = gtk_text_iter_get_toggled_tags (&iter, TRUE);
+
+          if (tags)
+            found_some = TRUE;
+          
+          tmp = tags;
+          while (tmp != NULL)
+            {
+              ++count;
+              
+              tag = tmp->data;
+
+              if (!g_hash_table_lookup (tag_states, tag))
+                g_error ("Tag %p was toggled on, but saw no off-toggle?", tag);
+
+              g_hash_table_remove (tag_states, tag);
+          
+              tmp = g_slist_next (tmp);
+            }
+
+          g_slist_free (tags);
+
+          if (!found_some)
+            g_error ("No tags found going backward to tag toggle.");
+        }
+      while (gtk_text_iter_backward_to_tag_toggle (&iter, NULL));
+    }
+  
+  g_hash_table_destroy (tag_states);
+
+  if (count != buffer_count)
+    g_error ("Counted %d tags iterating by char, %d iterating by tag toggle\n",
+             buffer_count, count);
+
+  check_specific_tag (buffer, "fg_red");
+  check_specific_tag (buffer, "bg_green");
+  check_specific_tag (buffer, "front_tag");
+  check_specific_tag (buffer, "center_tag");
+  check_specific_tag (buffer, "end_tag");
 }
 
 
@@ -264,7 +582,7 @@ fill_buffer (GtkTextBuffer *buffer)
   GtkTextIter iter2;
   GdkPixbuf *pixbuf;
   int i;
-  
+
   tag = gtk_text_buffer_create_tag (buffer, "fg_blue");
 
   color.red = color.green = 0;
@@ -297,49 +615,49 @@ fill_buffer (GtkTextBuffer *buffer)
                  NULL);
 
   pixbuf = gdk_pixbuf_new_from_xpm_data (book_closed_xpm);
-  
+
   g_assert (pixbuf != NULL);
-  
+
   i = 0;
   while (i < 10)
     {
       gchar *str;
 
       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
-      
+
       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
 
       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 1);
-      
+
       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
-      
+
       str = g_strdup_printf ("%d Hello World!\nwoo woo woo woo woo woo woo woo\n",
                             i);
-      
+
       gtk_text_buffer_insert (buffer, &iter, str, -1);
 
       g_free (str);
-      
+
       gtk_text_buffer_insert (buffer, &iter,
                               "(Hello World!)\nfoo foo Hello this is some text we are using to text word wrap. It has punctuation! gee; blah - hmm, great.\nnew line\n\n"
                               /* This is UTF8 stuff, Emacs doesn't
                                  really know how to display it */
                               "Spanish (Español) ¡Hola! / French (Français) Bonjour, Salut / German (Deutsch Süd) Grüß Gott (testing Latin-1 chars encoded in UTF8)\nThai (we can't display this, just making sure we don't crash)  (ภาษาไทย)  สวัสดีครับ, สวัสดีค่ะ\n",
-                              -1);  
-      
+                              -1);
+
       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
-      
+
       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 4);
-      
+
       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
 
       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 7);
-      
+
       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
 
       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 8);
-      
+
       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
 
       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 8);
@@ -350,40 +668,66 @@ fill_buffer (GtkTextBuffer *buffer)
 
       gtk_text_iter_forward_chars (&iter, 7);
       gtk_text_iter_forward_chars (&iter2, 10);
-      
+
       gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2);
 
       gtk_text_iter_forward_chars (&iter, 12);
       gtk_text_iter_forward_chars (&iter2, 10);
-      
+
       gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2);
 
       gtk_text_iter_forward_chars (&iter, 10);
       gtk_text_iter_forward_chars (&iter2, 15);
-      
+
       gtk_text_buffer_apply_tag_by_name (buffer, "fg_red", &iter, &iter2);
-      gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);      
+      gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);
 
       gtk_text_iter_forward_chars (&iter, 20);
       gtk_text_iter_forward_chars (&iter2, 20);
-      
+
       gtk_text_buffer_apply_tag_by_name (buffer, "fg_red", &iter, &iter2);
-      gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);      
+      gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);
 
       gtk_text_iter_backward_chars (&iter, 25);
       gtk_text_iter_forward_chars (&iter2, 5);
-      
+
       gtk_text_buffer_apply_tag_by_name (buffer, "fg_red", &iter, &iter2);
-      gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);      
+      gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);
 
       gtk_text_iter_forward_chars (&iter, 15);
       gtk_text_iter_backward_chars (&iter2, 10);
 
       gtk_text_buffer_remove_tag_by_name (buffer, "fg_red", &iter, &iter2);
-      gtk_text_buffer_remove_tag_by_name (buffer, "fg_blue", &iter, &iter2);      
-      
+      gtk_text_buffer_remove_tag_by_name (buffer, "fg_blue", &iter, &iter2);
+
       ++i;
     }
 
+  /* Put in tags that are just at the beginning, and just near the end,
+   * and just near the middle.
+   */
+  tag = gtk_text_buffer_create_tag (buffer, "front_tag");
+  gtk_text_buffer_get_iter_at_offset (buffer, &iter, 3);
+  gtk_text_buffer_get_iter_at_offset (buffer, &iter2, 300);
+
+  gtk_text_buffer_apply_tag (buffer, tag, &iter, &iter2);  
+  
+  tag = gtk_text_buffer_create_tag (buffer, "end_tag");
+  gtk_text_buffer_get_last_iter (buffer, &iter2);
+  gtk_text_iter_backward_chars (&iter2, 12);
+  iter = iter2;
+  gtk_text_iter_backward_chars (&iter, 157);
+
+  gtk_text_buffer_apply_tag (buffer, tag, &iter, &iter2);
+  
+  tag = gtk_text_buffer_create_tag (buffer, "center_tag");
+  gtk_text_buffer_get_iter_at_offset (buffer, &iter,
+                                      gtk_text_buffer_get_char_count (buffer)/2);
+  gtk_text_iter_backward_chars (&iter, 37);
+  iter2 = iter;
+  gtk_text_iter_forward_chars (&iter2, 57);
+
+  gtk_text_buffer_apply_tag (buffer, tag, &iter, &iter2);  
+
   gdk_pixbuf_unref (pixbuf);
 }
index 286f9b5fd7272384a6f53dfb4869d4b6bdf257af..437be8832f9c69c727088e786daf52be666818d9 100644 (file)
@@ -16,14 +16,14 @@ main (int argc, char** argv)
   int n;
   gunichar ch;
   GtkTextIter start, end;
-  
+
   gtk_init (&argc, &argv);
 
   /* Check UTF8 unknown char thing */
   g_assert (g_utf8_strlen (gtk_text_unknown_char_utf8, 3) == 1);
   ch = g_utf8_get_char (gtk_text_unknown_char_utf8);
   g_assert (ch == gtk_text_unknown_char);
-  
+
   /* First, we turn on btree debugging. */
   gtk_debug_flags |= GTK_DEBUG_TEXT;
 
@@ -31,18 +31,18 @@ main (int argc, char** argv)
   buffer = gtk_text_buffer_new (NULL);
 
   /* Check that buffer starts with one empty line and zero chars */
-  
+
   n = gtk_text_buffer_get_line_count (buffer);
   if (n != 1)
     g_error ("%d lines, expected 1", n);
-  
+
   n = gtk_text_buffer_get_char_count (buffer);
   if (n != 1)
     g_error ("%d chars, expected 1", n);
-  
+
   /* Run gruesome alien test suite on buffer */
   run_tests (buffer);
-  
+
   /* Put stuff in the buffer */
 
   fill_buffer (buffer);
@@ -59,7 +59,7 @@ main (int argc, char** argv)
   n = gtk_text_buffer_get_line_count (buffer);
   if (n != 1)
     g_error ("%d lines, expected 1", n);
-  
+
   n = gtk_text_buffer_get_char_count (buffer);
   if (n != 1)
     g_error ("%d chars, expected 1", n);
@@ -67,10 +67,165 @@ main (int argc, char** argv)
   run_tests (buffer);
 
   g_print ("All tests passed.\n");
-  
+
   return 0;
 }
 
+static gint
+count_toggles_at_iter (GtkTextIter *iter,
+                       GtkTextTag  *of_tag)
+{
+  GSList *tags;
+  GSList *tmp;
+  gint count = 0;
+  
+  /* get toggle-ons and toggle-offs */
+  tags = gtk_text_iter_get_toggled_tags (iter, TRUE);
+  tags = g_slist_concat (tags,
+                         gtk_text_iter_get_toggled_tags (iter, FALSE));
+  
+  tmp = tags;
+  while (tmp != NULL)
+    {
+      if (of_tag == NULL)
+        ++count;
+      else if (of_tag == tmp->data)
+        ++count;
+      
+      tmp = g_slist_next (tmp);
+    }
+  
+  g_slist_free (tags);
+
+  return count;
+}
+     
+static gint
+count_toggles_in_buffer (GtkTextBuffer *buffer,
+                         GtkTextTag    *of_tag)
+{
+  GtkTextIter iter;
+  gint count = 0;
+  
+  gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
+  do
+    {
+      count += count_toggles_at_iter (&iter, of_tag);
+    }
+  while (gtk_text_iter_next_char (&iter));
+
+  /* Do the end iterator, because forward_char won't return TRUE
+   * on it.
+   */
+  count += count_toggles_at_iter (&iter, of_tag);
+  
+  return count;
+}
+
+static void
+check_specific_tag (GtkTextBuffer *buffer,
+                    const gchar   *tag_name)
+{
+  GtkTextIter iter;
+  GtkTextTag *tag;
+  gboolean state;
+  gint count;
+  gint buffer_count;
+  gint last_offset;
+  
+  tag = gtk_text_tag_table_lookup (gtk_text_buffer_get_tag_table (buffer),
+                                   tag_name);
+
+  buffer_count = count_toggles_in_buffer (buffer, tag);
+  
+  state = FALSE;
+  count = 0;
+
+  last_offset = -1;
+  gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
+  if (gtk_text_iter_toggles_tag (&iter, tag) ||
+      gtk_text_iter_forward_to_tag_toggle (&iter, tag))
+    {
+      do
+        {
+          gint this_offset;
+          
+          ++count;
+
+          this_offset = gtk_text_iter_get_offset (&iter);
+
+          if (this_offset <= last_offset)
+            g_error ("forward_to_tag_toggle moved in wrong direction");
+
+          last_offset = this_offset;
+          
+          if (gtk_text_iter_begins_tag (&iter, tag))
+            {
+              if (state)
+                g_error ("Tag %p is already on, and was toggled on?", tag);
+              state = TRUE;
+            }          
+          else if (gtk_text_iter_ends_tag (&iter, tag))
+            {
+              if (!state)
+                g_error ("Tag %p toggled off, but wasn't toggled on?", tag);
+              state = FALSE;
+            }
+          else
+            g_error ("forward_to_tag_toggle went to a location without a toggle");
+        }
+      while (gtk_text_iter_forward_to_tag_toggle (&iter, tag));
+    }
+
+  if (count != buffer_count)
+    g_error ("Counted %d tags iterating by char, %d iterating by tag toggle\n",
+             buffer_count, count);
+  
+  state = FALSE;
+  count = 0;
+  
+  gtk_text_buffer_get_last_iter (buffer, &iter);
+  last_offset = gtk_text_iter_get_offset (&iter);
+  if (gtk_text_iter_toggles_tag (&iter, tag) ||
+      gtk_text_iter_backward_to_tag_toggle (&iter, tag))
+    {
+      do
+        {
+          gint this_offset;
+          
+          ++count;
+
+          this_offset = gtk_text_iter_get_offset (&iter);
+          
+          if (this_offset >= last_offset)
+            g_error ("backward_to_tag_toggle moved in wrong direction");
+          
+          last_offset = this_offset;
+
+          if (gtk_text_iter_begins_tag (&iter, tag))
+            {
+              if (!state)
+                g_error ("Tag %p wasn't on when we got to the on toggle going backward?", tag);
+              state = FALSE;
+            }
+          else if (gtk_text_iter_ends_tag (&iter, tag))
+            {
+              if (state)
+                g_error ("Tag %p off toggle, but we were already inside a tag?", tag);
+              state = TRUE;
+            }
+          else
+            g_error ("backward_to_tag_toggle went to a location without a toggle");
+        }
+      while (gtk_text_iter_backward_to_tag_toggle (&iter, tag));
+    }
+
+  if (count != buffer_count)
+    g_error ("Counted %d tags iterating by char, %d iterating by tag toggle\n",
+             buffer_count, count);
+
+}
+
 static void
 run_tests (GtkTextBuffer *buffer)
 {
@@ -81,9 +236,13 @@ run_tests (GtkTextBuffer *buffer)
   gint i, j;
   gint num_chars;
   GtkTextMark *bar_mark;
+  GtkTextTag *tag;
+  GHashTable *tag_states;
+  gint count;
+  gint buffer_count;
   
   gtk_text_buffer_get_bounds (buffer, &start, &end);
-  
+
   /* Check that walking the tree via chars and via iterators produces
    * the same number of indexable locations.
    */
@@ -95,7 +254,7 @@ run_tests (GtkTextBuffer *buffer)
     {
       GtkTextIter current;
       GtkTextMark *foo_mark;
-      
+
       gtk_text_buffer_get_iter_at_offset (buffer, &current, i);
 
       if (!gtk_text_iter_equal (&iter, &current))
@@ -119,11 +278,11 @@ run_tests (GtkTextBuffer *buffer)
           gtk_text_iter_spew (&mark, "mark");
           g_error ("Mark not moved to the right place.");
         }
-      
+
       foo_mark = gtk_text_buffer_create_mark (buffer, "foo", &iter, FALSE);
       gtk_text_buffer_get_iter_at_mark (buffer, &mark, foo_mark);
       gtk_text_buffer_delete_mark (buffer, foo_mark);
-      
+
       if (!gtk_text_iter_equal (&iter, &mark))
         {
           gtk_text_iter_spew (&iter, "iter");
@@ -134,34 +293,34 @@ run_tests (GtkTextBuffer *buffer)
       if (gtk_text_iter_is_last (&iter))
         g_error ("iterators ran out before chars (offset %d of %d)",
                  i, num_chars);
-      
+
       gtk_text_iter_next_char (&iter);
 
       gtk_text_buffer_move_mark (buffer, bar_mark, &iter);
-      
+
       ++i;
     }
 
   if (!gtk_text_iter_equal (&iter, &end))
     g_error ("Iterating over all chars didn't end with the end iter");
 
-  /* Do the tree-walk backward 
+  /* Do the tree-walk backward
    */
   num_chars = gtk_text_buffer_get_char_count (buffer);
   gtk_text_buffer_get_iter_at_offset (buffer, &iter, -1);
 
   gtk_text_buffer_move_mark (buffer, bar_mark, &iter);
-  
+
   i = num_chars;
 
   if (!gtk_text_iter_equal (&iter, &end))
     g_error ("iter at char -1 is not equal to the end iterator");
-  
+
   while (i >= 0)
     {
       GtkTextIter current;
       GtkTextMark *foo_mark;
-      
+
       gtk_text_buffer_get_iter_at_offset (buffer, &current, i);
 
       if (!gtk_text_iter_equal (&iter, &current))
@@ -184,18 +343,18 @@ run_tests (GtkTextBuffer *buffer)
           gtk_text_iter_spew (&mark, "mark");
           g_error ("Mark not moved to the right place.");
         }
-      
+
       foo_mark = gtk_text_buffer_create_mark (buffer, "foo", &iter, FALSE);
       gtk_text_buffer_get_iter_at_mark (buffer, &mark, foo_mark);
       gtk_text_buffer_delete_mark (buffer, foo_mark);
-      
+
       if (!gtk_text_iter_equal (&iter, &mark))
         {
           gtk_text_iter_spew (&iter, "iter");
           gtk_text_iter_spew (&mark, "mark");
           g_error ("Mark not created in the right place.");
         }
-      
+
       if (i > 0)
         {
           if (!gtk_text_iter_prev_char (&iter))
@@ -208,10 +367,10 @@ run_tests (GtkTextBuffer *buffer)
           if (gtk_text_iter_prev_char (&iter))
             g_error ("went backward from 0?");
         }
-      
+
       --i;
     }
-  
+
   if (!gtk_text_iter_equal (&iter, &start))
     g_error ("Iterating backward over all chars didn't end with the start iter");
 
@@ -223,10 +382,169 @@ run_tests (GtkTextBuffer *buffer)
   gtk_text_buffer_get_iter_at_line (buffer, &iter, 0);
   while (gtk_text_iter_forward_line (&iter))
     ++i;
-  
+
   if (i != gtk_text_buffer_get_line_count (buffer))
     g_error ("Counted %d lines, buffer has %d", i,
              gtk_text_buffer_get_line_count (buffer));
+
+  /*
+   * Check that moving over tag toggles thinks about working.
+   */
+
+  buffer_count = count_toggles_in_buffer (buffer, NULL);
+  
+  tag_states = g_hash_table_new (NULL, NULL);
+  count = 0;
+  
+  gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
+  if (gtk_text_iter_toggles_tag (&iter, NULL) ||
+      gtk_text_iter_forward_to_tag_toggle (&iter, NULL))
+    {
+      do
+        {
+          GSList *tags;
+          GSList *tmp;
+          gboolean found_some = FALSE;
+          
+          /* get toggled-on tags */
+          tags = gtk_text_iter_get_toggled_tags (&iter, TRUE);
+
+          if (tags)
+            found_some = TRUE;
+          
+          tmp = tags;
+          while (tmp != NULL)
+            {
+              ++count;
+              
+              tag = tmp->data;
+              
+              if (g_hash_table_lookup (tag_states, tag))
+                g_error ("Tag %p is already on, and was toggled on?", tag);
+
+              g_hash_table_insert (tag_states, tag, GINT_TO_POINTER (TRUE));
+          
+              tmp = g_slist_next (tmp);
+            }
+
+          g_slist_free (tags);
+      
+          /* get toggled-off tags */
+          tags = gtk_text_iter_get_toggled_tags (&iter, FALSE);
+
+          if (tags)
+            found_some = TRUE;
+          
+          tmp = tags;
+          while (tmp != NULL)
+            {
+              ++count;
+              
+              tag = tmp->data;
+
+              if (!g_hash_table_lookup (tag_states, tag))
+                g_error ("Tag %p is already off, and was toggled off?", tag);
+
+              g_hash_table_remove (tag_states, tag);
+          
+              tmp = g_slist_next (tmp);
+            }
+
+          g_slist_free (tags);
+
+          if (!found_some)
+            g_error ("No tags found going forward to tag toggle.");
+
+        }
+      while (gtk_text_iter_forward_to_tag_toggle (&iter, NULL));
+    }
+  
+  g_hash_table_destroy (tag_states);
+
+  if (count != buffer_count)
+    g_error ("Counted %d tags iterating by char, %d iterating by tag toggle\n",
+             buffer_count, count);
+  
+  /* Go backward; here TRUE in the hash means we saw
+   * an off toggle last.
+   */
+  
+  tag_states = g_hash_table_new (NULL, NULL);
+  count = 0;
+  
+  gtk_text_buffer_get_last_iter (buffer, &iter);
+  if (gtk_text_iter_toggles_tag (&iter, NULL) ||
+      gtk_text_iter_backward_to_tag_toggle (&iter, NULL))
+    {
+      do
+        {
+          GSList *tags;
+          GSList *tmp;
+          gboolean found_some = FALSE;
+          
+          /* get toggled-off tags */
+          tags = gtk_text_iter_get_toggled_tags (&iter, FALSE);
+
+          if (tags)
+            found_some = TRUE;
+          
+          tmp = tags;
+          while (tmp != NULL)
+            {
+              ++count;
+              
+              tag = tmp->data;
+
+              if (g_hash_table_lookup (tag_states, tag))
+                g_error ("Tag %p has two off-toggles in a row?", tag);
+          
+              g_hash_table_insert (tag_states, tag, GINT_TO_POINTER (TRUE));
+          
+              tmp = g_slist_next (tmp);
+            }
+
+          g_slist_free (tags);
+      
+          /* get toggled-on tags */
+          tags = gtk_text_iter_get_toggled_tags (&iter, TRUE);
+
+          if (tags)
+            found_some = TRUE;
+          
+          tmp = tags;
+          while (tmp != NULL)
+            {
+              ++count;
+              
+              tag = tmp->data;
+
+              if (!g_hash_table_lookup (tag_states, tag))
+                g_error ("Tag %p was toggled on, but saw no off-toggle?", tag);
+
+              g_hash_table_remove (tag_states, tag);
+          
+              tmp = g_slist_next (tmp);
+            }
+
+          g_slist_free (tags);
+
+          if (!found_some)
+            g_error ("No tags found going backward to tag toggle.");
+        }
+      while (gtk_text_iter_backward_to_tag_toggle (&iter, NULL));
+    }
+  
+  g_hash_table_destroy (tag_states);
+
+  if (count != buffer_count)
+    g_error ("Counted %d tags iterating by char, %d iterating by tag toggle\n",
+             buffer_count, count);
+
+  check_specific_tag (buffer, "fg_red");
+  check_specific_tag (buffer, "bg_green");
+  check_specific_tag (buffer, "front_tag");
+  check_specific_tag (buffer, "center_tag");
+  check_specific_tag (buffer, "end_tag");
 }
 
 
@@ -264,7 +582,7 @@ fill_buffer (GtkTextBuffer *buffer)
   GtkTextIter iter2;
   GdkPixbuf *pixbuf;
   int i;
-  
+
   tag = gtk_text_buffer_create_tag (buffer, "fg_blue");
 
   color.red = color.green = 0;
@@ -297,49 +615,49 @@ fill_buffer (GtkTextBuffer *buffer)
                  NULL);
 
   pixbuf = gdk_pixbuf_new_from_xpm_data (book_closed_xpm);
-  
+
   g_assert (pixbuf != NULL);
-  
+
   i = 0;
   while (i < 10)
     {
       gchar *str;
 
       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
-      
+
       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
 
       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 1);
-      
+
       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
-      
+
       str = g_strdup_printf ("%d Hello World!\nwoo woo woo woo woo woo woo woo\n",
                             i);
-      
+
       gtk_text_buffer_insert (buffer, &iter, str, -1);
 
       g_free (str);
-      
+
       gtk_text_buffer_insert (buffer, &iter,
                               "(Hello World!)\nfoo foo Hello this is some text we are using to text word wrap. It has punctuation! gee; blah - hmm, great.\nnew line\n\n"
                               /* This is UTF8 stuff, Emacs doesn't
                                  really know how to display it */
                               "Spanish (Español) ¡Hola! / French (Français) Bonjour, Salut / German (Deutsch Süd) Grüß Gott (testing Latin-1 chars encoded in UTF8)\nThai (we can't display this, just making sure we don't crash)  (ภาษาไทย)  สวัสดีครับ, สวัสดีค่ะ\n",
-                              -1);  
-      
+                              -1);
+
       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
-      
+
       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 4);
-      
+
       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
 
       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 7);
-      
+
       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
 
       gtk_text_buffer_get_iter_at_offset (buffer, &iter, 8);
-      
+
       gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
 
       gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 8);
@@ -350,40 +668,66 @@ fill_buffer (GtkTextBuffer *buffer)
 
       gtk_text_iter_forward_chars (&iter, 7);
       gtk_text_iter_forward_chars (&iter2, 10);
-      
+
       gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2);
 
       gtk_text_iter_forward_chars (&iter, 12);
       gtk_text_iter_forward_chars (&iter2, 10);
-      
+
       gtk_text_buffer_apply_tag_by_name (buffer, "bg_green", &iter, &iter2);
 
       gtk_text_iter_forward_chars (&iter, 10);
       gtk_text_iter_forward_chars (&iter2, 15);
-      
+
       gtk_text_buffer_apply_tag_by_name (buffer, "fg_red", &iter, &iter2);
-      gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);      
+      gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);
 
       gtk_text_iter_forward_chars (&iter, 20);
       gtk_text_iter_forward_chars (&iter2, 20);
-      
+
       gtk_text_buffer_apply_tag_by_name (buffer, "fg_red", &iter, &iter2);
-      gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);      
+      gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);
 
       gtk_text_iter_backward_chars (&iter, 25);
       gtk_text_iter_forward_chars (&iter2, 5);
-      
+
       gtk_text_buffer_apply_tag_by_name (buffer, "fg_red", &iter, &iter2);
-      gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);      
+      gtk_text_buffer_apply_tag_by_name (buffer, "fg_blue", &iter, &iter2);
 
       gtk_text_iter_forward_chars (&iter, 15);
       gtk_text_iter_backward_chars (&iter2, 10);
 
       gtk_text_buffer_remove_tag_by_name (buffer, "fg_red", &iter, &iter2);
-      gtk_text_buffer_remove_tag_by_name (buffer, "fg_blue", &iter, &iter2);      
-      
+      gtk_text_buffer_remove_tag_by_name (buffer, "fg_blue", &iter, &iter2);
+
       ++i;
     }
 
+  /* Put in tags that are just at the beginning, and just near the end,
+   * and just near the middle.
+   */
+  tag = gtk_text_buffer_create_tag (buffer, "front_tag");
+  gtk_text_buffer_get_iter_at_offset (buffer, &iter, 3);
+  gtk_text_buffer_get_iter_at_offset (buffer, &iter2, 300);
+
+  gtk_text_buffer_apply_tag (buffer, tag, &iter, &iter2);  
+  
+  tag = gtk_text_buffer_create_tag (buffer, "end_tag");
+  gtk_text_buffer_get_last_iter (buffer, &iter2);
+  gtk_text_iter_backward_chars (&iter2, 12);
+  iter = iter2;
+  gtk_text_iter_backward_chars (&iter, 157);
+
+  gtk_text_buffer_apply_tag (buffer, tag, &iter, &iter2);
+  
+  tag = gtk_text_buffer_create_tag (buffer, "center_tag");
+  gtk_text_buffer_get_iter_at_offset (buffer, &iter,
+                                      gtk_text_buffer_get_char_count (buffer)/2);
+  gtk_text_iter_backward_chars (&iter, 37);
+  iter2 = iter;
+  gtk_text_iter_forward_chars (&iter2, 57);
+
+  gtk_text_buffer_apply_tag (buffer, tag, &iter, &iter2);  
+
   gdk_pixbuf_unref (pixbuf);
 }